在C++中,动态内存的管理是用一对运算符完成的:new和delete,分别用来申请动态内存和释放。动态内存管理经常会出现两种问题:一种是忘记释放内存,会造成内存泄漏;一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。而为了更好的管理动态内存,不出现上面两种问题,C++引入了智能指针的概念。
智能指针,名字听起来很容易让人以为它是指针,实则不然。它其实是披着指针外壳的类,它可以看作RAII技术的一种应用。RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化”,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
智能指针比较有代表性的主要有两个,分别是shared_pre和unique_ptr。从名字就能看出,前者可以共享,允许多个智能指针指向同一个对象,相应的就提出了引用技术的概念;后者则是霸道地独占所指对象。
①初始化 关于初始化不得不提的一点是,不可以用普通的指针来初始化一个智能指针,这一规则其他智能指针类也是通用的 默认初始化
shared_ptr<string> p1; //shared_ptr,可以指向string shared_ptr<vector<int>> p2; //shared_ptr,可以指向int类型的向量 share_ptr<int> p3(new int (5)) //直接指向动态生成的对象使用make_shared函数进行初始化
//需要注意的是,两边的类型必须一致 shared_ptr<int> p3 = make_shared<int>(42); //P3指向一个值为42的int shared_ptr<int> p3 = make_shared<int>(); //指向一个int,值默认为0②拷贝和赋值 当进行拷贝和赋值时,每个shared_ptr都会记录有多少个“同类”和自己指向相同的对象,因为每个shared_ptr的对象内部都含有一个关联计数器,通常也叫引用计数。每当出现拷贝一个shared_ptr的情况,计算器都会递增。例如,用一个shared_ptr去初始化另一个shared_ptr,或者将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。看到上面的三种情况,如果对类的复制构造函数比较了解的话很容易发现,这正是调用复制构造函数的三种情况。(还没看代码,盲猜一波引用计数就是利用复制构造函数里的自增实现的) 上面将的都是引用计数增加的情况,那么减少呢? 当我们给一个shared_ptr赋予新值或者shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计算器就会递减。特殊地,当我们将指针设为null时,其引用计数也会减1
shared_ptr<int> p1=make_shared<int>(42); //初始化引用计数为1 shared_ptr<int> p2=p1; //好像也能叫初始化,反正计数变成2 shared_ptr<int> p3=make_shared<int>(20);//p3的引用计数为1 p2=p3; //由于P2“变心”跟p3跑了,此时p1对应的引用计数减小,变成1; //而p3对应的引用计数增加,变成2。 //此处可以很容易发现应用计数就是指向同一对对象的shared_ptr个数③自动销毁所指对象 当引用计数减小到0时,自动销毁所指向的对象,直接上例子
//Date是一个时间类,通过fun函数返回一个智能指针,用来管理动态生成的对象 shared_ptr<Date>fun(int time){ return make_shared<Date>(time); }由于fun函数返回的是一个shared_ptr,下面将验证一些智能指针在什么情况下会将对象进行释放。
void use_fun(int time) { shared_ptr<Date> p=fun(time); }由于P是ues_fun函数的局部变量,在函数结束后该智能指针就会被销毁,而函数内执行的语句生成了一个Date类的对象,本来是交由智能指针P来管理的,而且只有这一个指针指向了它,因此,在P被销毁的同时,P指向的这个刚刚动态生成的对象也会被销毁。
shared_ptr<Date> use_fun(int time) { shared_ptr<Date> p=fun(time); return P; }如果改成上面这种形式,由于函数返回了一个智能指针,导致其引用计数增加1,离开函数后其引用计数还是1,因此虽然P被销毁了,但是那个动态生成的对象还是,将会有新的指针对其进行引用(函数返回值肯定会被赋予到调用函数的某个值上去)。
关于unique_ptr,简单谈一下和shared_ptr的区别 由于它没有类似make_shared的函数,而且unique_ptr对其所指对象是独占的,自然不能用一个unique_ptr来初始化另一个unique_ptr.所以要初始化一个unique_ptr,主要有三种方式:采用默认初始化;直接将其绑定到一个new返回的指针上;使用release函数实现控制权转移,下面是实例:
unique_ptr<double> p1; //可以指向一个double的unique_ptr unique_ptr<int> p2(new int(42)); //p2指向一个值为42的int auto p3=p2.release(); //将p2对int的控制权转移给p3,p2置为空 p2.release() //错误使用,p2被置为空,其所指对象仍然存在且将无法管理weak_ptr,顾名思义,它的属性比较弱,是一种不控制所指对象生存周期的之智能指针。当一个weak_ptr绑定到shared_ptr所指向的对象时,不会改变shared_ptr的引用计数,一旦shared_ptr的引用计数减小到0,对象就会被销毁,完全忽略weak_ptr的存在。weak_ptr不能直接访问其所指对象(前面提到weak_ptr的对象可能已经销毁),必须调用lock函数。如果对象存在,lock函数会返回一个指向对象的shared_ptr,只要shared_ptr还存在,就说明对象还存在
auto p =make_shared<int>(42); weak_ptr<int>wp(p); //用shared_ptr初始化weak_ptr ... ... if(shared_ptr<int> np =wp.lock(){ //np不为空则说明wp所指的对象还存在 ... //在if中,np与p共享对象 ... }