new/delete探秘
new/delete不是函数,而是关键字/运算符。malloc/free用于c语言,new/delete用于c++。这两对都用于动态的在堆中分配和释放内存,new/delete比malloc/free干的事情更多,new/delete类对象时可以调用类对象的构造函数和析构函数,说明具备对堆上所分配内存进行初始化/释放的能力,malloc/free不具备。
operator new()和operator delete()是函数
new干了两件事:
1)分配内存,通过operator new()来分配内存
2)调用构造函数初始化内存
delete干了两件事:
1)调用析构函数
2)释放内存,通过operator delete()来释放内存
class A{
public:
A(){};
~A(){};
}
int *pa = new int(100); //泄露4字节
int *pa2 = new int[2]; //泄露8字节
A *pA = new A(); //泄露1字节
A *pA2 = new A[2](); //泄露6字节,多了四个字节
int *pa3 = new int[3];
delete pa3; //没有用[],pa3没有发生内存泄露,正常为delete[] pa3;
A *pA3 = new A[2]();
delete pA3; //系统异常,规范写法delete[] pA3;
动态给类型A分配内存对象数组时多出来4个字节,而给内置类型int动态分配内存对象数组时没有。多出来的4个字节,用来记录数组元素的个数用来调用析构函数,无自定义的析构函数可以不带[],内置类型不需要调用析构函数,所以没有多4个字节
智能指针
int *p = new int();
int *q = p;
int *r = q;//只有p,q,r都不使用,才能释放这段内存
裸指针:直接用new返回的指针,这种指针强大、灵活,但是开发者需要全程负责维护,容易用错
智能指针:解决裸指针可能存在的各种问题,最突出的优点是自动释放所指向的对象内存。标准库中有4种智能指针,分别是auto_ptr,unique_ptr,shared_ptr,weak_ptr
,帮助我们动态分配对象的声明周期的管理,能够有效防止内存泄露
auto_ptr(c++98),已经完全被unique_ptr取代,不要继续使用,剩下三种都是类模板,可以将new获得的地址赋给他们
share_ptr
:共享式指针,多个指针指向同一个对象,最后一个指针被销毁时,这个对象会被释放
weak_ptr
:是辅助share_ptr工作的
unique_ptr
:独占式指针,同一个时间内,只有一个指针能够指向该对象,该对象的所有权是可以移交出去的
你忘记delete的时候,智能指针能够帮助你delete
share_ptr
共享所有权,不是被一个share_ptr拥有,而是被多个share_ptr之间相互写作;share_ptr有额外开销;工作原理是引用计数,每个share_ptr的拷贝都指向相同的内存。所以,只有最后一个指针不需要指向该对象时,这个对象会被释放。
最后一个指向该内存对象的share_ptr在下列情况下会释放该对象
1)这个share_ptr被析构的时候
2)这个share_ptr指向其他对象
格式:share_ptr<指向的类型> 智能指针名
share_ptr<int> pi(new int(100));
share_ptr<int> pi2=new int(100); //不可以,智能指针是explicit,不可以进行隐式类型转换,必须直接初始化
share_ptr<int> makes(int value){
return share_ptr<int>(new int(value));
}
make_shared函数
标准库里的函数模板,安全,高效的分配和使用shared_ptr;它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared_ptr,不能指定自己的删除器
shared_ptr<int> p2 = make_shared<int>(100);
shared_ptr<string> p3 = make_shared<string>(5,'a'); //aaaaa
不能自定义删除器
shared_ptr引用计数的增加和减少
引用计数的增加:每个shared_ptr都会记录有多少个其他的shared_ptr指向相同的对象,在如下情况下,所有指向这个对象的shared_ptr引用计数都会增加
1)如下代码所示,用p6来初始化p7这个智能指针
2)把智能指针当作实参往函数中传递
3)作为函数返回值
void myfunc(shared_ptr<int> ptmp){
return;
}
void myfunc2(shared_ptr<int> &ptmp){
return;
}
shared_ptr<int> myfunc3(shared_ptr<int> &ptmp){
return ptmp;
}
auto p6 = make_shared<int>(100);
auto p7(p6); //智能指针定义时的初始化,p7和p6指向了相同的对象,引用计数增加
myfunc(p6); //进入时引用计数增加,退出时引用计数减少
myfunc2(p6); //引用计数不增加
auto p8 = myfunc3(p6); //引用计数增加
引用计数的减少:
1)给shared_ptr赋予新值,让该shared_ptr指向一个新对象
2)局部的shared_ptr离开作用域
3)当一个shared_ptr引用计数从1变到0,则他会自动释放自己所管理的对象
p8 = make_shared<int>(200); //p8指向新对象,计数为1,p6,p7引用计数减少
p9 = make_shared<int>(200);
p9 = p8; //给p9赋值会让p9指向p8所指向的对象,p8指向的对象引用计数变为2,p9之前指向的对象引用计数从1变为0
shared_ptr常用操作
use_count():返回多少个智能指针指向某个对象,主要用于调试
unique():是否该只能指针独占某个指向的对象,也就是说若只有一个智能指针指向某个对象,则unique()返回true
reset():重置,1)不带参数时,若pi是唯一指向该对象的指针,那么释放pi所指向的对象,并将pi置空;若pi不是唯一指向该对象的指针,那么不释放pi所指向的对象,但指向该对象的引用计数会减少1,并将pi置空
2)带参数时,若pi是唯一指向该对象的指针,那么释放pi所指向的对象,并将pi指向新对象;若pi不是唯一指向该对象的指针,那么不释放pi所指向的对象,但指向该对象的引用计数会减少1,并将pi指向新对象
解引用*:获得p指向的对象,与普通指针相同
get():返回p中保存的指针(裸指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针与人就变得不可用。不能delete保存的裸指针
swap():交换两个智能指针所指向的对象
p1 = make_shared<string>("test1");
p2 = make_shared<string>("test2");
std::swap(p1,p2);
p1.swap(p2)
=nullptr:1)将所指向的对象引用计数减1,若引用计数变为0,则释放智能指针所指向的对象
2)将智能指针置空
指定删除器以及数组问题:
指定删除器,一定时机帮我们删除所指向对象;
void mydelete(int *p){ //我们的删除器,删除整形指针使用,当智能指针引用计数为0,就会调用该删除器删除对象
delete p;
}
shared_ptr<int> p(new int(1234),mydelete);
shared_ptr<int> p2(p);
p2.reset();
p.reset();
//删除器可以是lambda表达式
shared_ptr<int> p3(new int(1234),[](int *p){
delete p;
});
shared_ptr<int> p4(new int[10],[](int *p){
delete []p;
});
shared_ptr<int> p5(new int[10], std::default_delete(int[]());
shared_ptr<int[]> p6(new int[10]);
就算两个shared_ptr指定了不同的删除器,只要他们指向的对象类型相同,那么这两个shared_ptr也属于同一个类型
weak_ptr
weak_ptr辅助shared_ptr工作
weak_ptr也是一个类模板,也是个智能指针。这个智能指针指向一个由shared_ptr管理的对象,但是weak_ptr这种指针不控制所指向的对象的生存周期。换句话说,将weak_ptr绑定到shared_ptr上不会增加shared_ptr的引用计数。当shared_ptr需要释放所指定对象的时候照常释放,不管是否有weak_ptr指向该对象
auto pi = make_shared<int>(100);
weak_ptr<int> piw(pi); //pi强引用计数不改变,弱引用计数加1
//强引用计数才能决定对象生存期,弱引用计数对生存期没有影响,只是监视作用
lock()
:功能是检查weak_ptr所指向的对象是否存在,如果存在,那么就返回一个指向该对象的shared_ptr(强引用计数加1);如果不存在,则返回一个空的shared_ptr
use_count()
:获取与该弱指针共享对象的其他shared_ptr的数量,获得当前所观测资源的强引用计数
expired()
:是否过期,弱指针use_count()为0,则返回true;判断所观测资源是否已经被释放
reset()
:将该弱引用指针设置为空,不影响指向该对象的强引用数量,但指向该对象的弱引用数量会减少
裸指针内存为4字节,shared_ptr,weak_ptr内存为8字节,包含一个指向类型对象的裸指针和一个指向控制块的指针,控制块中含有所指对象的强引用计数、弱引用计数、其他数据等
shared_ptr陷阱分析
慎用裸指针
void proc(shared_ptr<int> ptr)
{
return;
}
int *p = new int(100);
proc(p);//报错,不能隐式转换
proc(shared_ptr<int>(p));//参数是一个临时shared_ptr,用一个裸指针显示构造
*p = 45; //潜在的不可预料的问题,因为p指向的内存已经被释放
shared_ptr<int> p2(p); //使用裸指针初始化智能指针后,不应该再使用裸指针来访问相应的内存
不要用裸指针初始化多个shared_ptr,因为初始化的多个shared_ptr无关联,释放时每一个都会自动释放,造成同一块内存被多次释放
慎用get()返回地指针
get()的作用是返回智能指针所对应的裸指针,返回地指针不能delete,不能将其他智能指针绑定到get返回地指针上
不要把类对象指针this作为shared_ptr返回,改用enable_shared_from_this
class CT
{
public:
shared_ptr<CT> getself()
{
return shared_ptr<CT>(this);//相当于用裸指针初始化了多个shared_ptr
}
}
shared_ptr<CT> pct1(new CT);
shared_ptr<CT> pct2 = pct1; //正常,两个强引用
shared_ptr<CT> pct3 = pct1->getself(); //出现问题,pct1和pct3无关联,指向同一块内存
//正常返回只能指针
class CT : public enable_shared_from_this<CT>
{
public:
shared_ptr<CT> getself()
{
return shared_from_this(); //返回智能指针
}
}
再调用shared_from_this()这个方法时内部实际调用了lock()方法,使得shared_ptr指针计数加1,同时返回这个shared_ptr
避免循环引用
unique_ptr
独占式指针,同一时刻只能有一个unique_ptr指针指向这个对象;当这个unique_ptr被销毁时,他所指向的对象也被销毁
unique_ptr<int> pi(new int(100));
make_unique()
c11中没有,c14加入,不支持指定的删除器语法,如果不使用删除器,建议优先使用。
unique_ptr<int> p1 = make_unique<int>(100);
auto p2 = make_unique<int>(100);
unique_ptr不支持的操作
不支持赋值、拷贝
unique_ptr<string> ps1(new string("i love china"));
unique_ptr<string> ps2(ps1); //不支持
unique_ptr<string> ps3 = ps1; //不支持
unique_ptr常用操作
移动语义
:
unique_ptr<string> ps1(new string("i love china"));
unique_ptr<string> ps2 = std::move(ps1); //移动完成后,ps1为空,ps2指向之前ps1所指
release()
:放弃对指针的控制权。返回裸指针,将该裸指针置空;返回地这个裸指针我们可以手工delete来释放,也可以用来初始化另外一个智能指针
unique_ptr<string> ps1(new string("i love china"));
ps1.release(); //内存泄露
string *temp = ps1.release();
delete temp;
reset()
:不带参数时,释放智能指针所指向的对象,并将智能指针置空;带参数时,释放智能指针所指向的对象,并让该智能指针指向新对象
=nullptr
:释放智能指针所指向的对象,并将智能指针置空
指向一个数组
:
unique_ptr<int> ptrarray(new int[10]);
ptrarray[0] = 1;
ptrarray[1] = 2;
class A
{
public:
A(){};
~A(){};
}
unique_ptr<A> ptrarray(new A[10]); //因为自定义析构函数,所以报异常
unique_ptr<A[10]> ptrarray(new A[10]); //不报异常
get()
:返回智能指针中的裸指针
*解引用
:获取该智能指针指向的对象
swap()
:交换两个智能指针指向的对象
转换为shared_ptr类型
:如果unique_ptr为右值,就可以把他赋值给shared_ptr。因为shared_ptr包含一个显式的构造函数,可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unique_ptr所拥有的内存
unique_ptr<string> ps1(new string("i love china"));
shared_ptr<string> ps2 = std::move(ps1);
返回unique_ptr
unique_ptr<string> tuniqp(){
unique_ptr<string> pr(new string("i love china"));
return pr;
}
unique_ptr<string> ps = tuniqp(); //临时对象直接构造在ps中,,如果不接,则临时对象会被释放,同时释放相应的内存
指定删除器
delete相当于默认删除器
void mydelete(string *pdel){
delete pdel;
pdel = nullptr;
}
typedef void(*fp)(string*);
unique_ptr<string,fp> ps1(new string("i love china"),mydelete);
//第二种写法
using fp2 = void(*)(string *);
unique_ptr<string,fp2> ps1(new string("i love china"),mydelete);
//第三种写法
typedef decltype(mydelete)* fp3;
unique_ptr<string,fp3> ps1(new string("i love china"),mydelete);
//第四种写法
unique_ptr<string,decltype(mydelete)*> ps1(new string("i love china"),mydelete);
//第五种写法,lambda表达式
auto mydella = [](string *pdel){
delete pdel;
pdel = nullptr;
}
unique_ptr<string,decltype(mydella)> ps1(new string("i love china"),mydella);
shared_ptr就算两个shared_ptr指定的删除器不同,只要他们所指向的对象相同,那么这两个shared_ptr也属于同一个类型
但是unique_ptr不一样,指定unique_ptr的删除器会影响unique_ptr的类型
尺寸问题
通常情况下,unique_ptr尺寸与裸指针一样,应该为4字节;如果增加了自己的删除器,则unique_ptr的尺寸可能增加,也可能不变,lambda表达式为删除器,则尺寸不变;定义一个函数为删除器,则尺寸增加。
shared_ptr不管指定什么删除器,尺寸为裸指针的2倍
总结
智能指针的主要目的是:帮助我们释放内存,以防止我们忘记释放内存造成内存泄露(设计思想)
auto_ptr被废弃:不能再容器中使用;不能从函数中返回auto_ptr;具有unique_ptr的部分特性
如果程序要使用多个指向同一个对象的指针,应该选择shared_ptr;
如果程序不需要多个指向同一个对象的指针,应该选择unique_ptr,性能更好;
评论区