本文共 9820 字,大约阅读时间需要 32 分钟。
unique_ptr
以替代auto_ptr
auto_ptr
是C++98标准库提供的一个智能指针,但已被C++11明确声明不再支持。auto_ptr
具有以下缺陷:
auto_ptr
有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr
则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()
进行转移。 #include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< auto_ap(new A("auto_ptr")),auto_bp; cout< < unique_ap(new A("unique_ptr")),unique_bp; cout< <
运行结果:
auto_ptr:构造函数0x6115d00unique_ptr:构造函数0x6115f00unique_ptr:析构函数auto_ptr:析构函数
auto_ptr
不可作为容器元素,unique_ptr
可以作为容器元素。因为auto_ptr
的拷贝和赋值具有破坏性,不满足容器要求:拷贝或赋值后,两个对象必须具有相同值。auto_ptr
不可指向动态数组,unique_ptr
可以指向动态数组。因为unique_ptr
有unique_ptr<T[]>
重载版本,销毁动态对象时调用delete[]
。#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< auto_ap(new A[1]{A("unique_ptr")}); // 报错 unique_ptr unique_ap(new A[1]{A("unique_ptr")}); return 0;}
运行结果:
unique_ptr:构造函数unique_ptr:析构函数
auto_ptr
不可以自定义删除器deleter
,而unique_ptr
可以。#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< unique_ap(new A[2]{A("unique_ptr0"),A("unique_ptr1")}, [](A *a){ delete []a; }); return 0;}
运行结果:
unique_ptr0:构造函数unique_ptr1:构造函数unique_ptr1:析构函数unique_ptr0:析构函数
unique_ptr
而非shared_ptr
默认情况下,应使用unique_ptr
,理由如下:
unique_ptr
,当需要共享对象所有权时,依然可以将其转化为shared_ptr
,但反过来则不行。shared_ptr
需要消耗更多的资源,shared_ptr
需要维护一个指向动态内存对象的线程安全的引用计数器以及背后的一个控制块,这使它比unique_ptr
更加复杂。shared_ptr
有可能造成其他程序员无意间通过赋值给另一个共享指针而修改了你共享出来的对象。#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< a(new A("unique_ptr")); shared_ptr b = move(a);// a = move(b); // 报错// a.reset(b.get()); // 运行错误 cout< <
运行结果:
unique_ptr:构造函数0unique_ptr:析构函数
当需要裸指针与智能指针搭配使用时,需要避免如下操作:
delete
操作以上操作会导致程序再次尝试销毁已被销毁了的对象,进而造成程序崩溃。所以,当使用裸指针初始化智能指针后,应确保裸指针永远不应该被再次使用。
#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< unique_pa(pa);// delete pa; // 运行错误 A *pb = new A("ptrB"); unique_ptr unique_pb1(pb);// unique_ptr unique_pb2(pb); // 运行错误 return 0;}
不要使用静态分配对象的指针初始化智能指针,否则,当智能指针本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< pa(&a); // 运行错误 unique_ptr pa(&b); return 0;}
运行结果:
全局变量:构造函数局部变量:构造函数局部变量:析构函数局部变量:析构函数全局变量:析构函数
unique_ptr
可以作为函数返回值尽管unique_ptr
无拷贝语义,但提供了移动语义,所以可作为函数返回值。
#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< fun(){ cout<<"==>fun()"< pa(new A("unique_ptr")); cout<<"<==fun()"< main()"<
运行结果:
==>main()==>fun()unique_ptr:构造函数<==fun()<==main()unique_ptr:析构函数
get
与release
方法当使用get
方法返回裸指针时,智能指针并没有释放指向对象的所有权,所以我们必须小心使用裸指针以避免程序崩溃。
但通过unique_ptr.release()
方法返回的裸指针,需要我们自己delete
删除对象,因为调用release
方法后,该unique_ptr
不再拥有对象的所有权。
#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< unique_pa(new A("unique_ptr")); A *pa = unique_pa.get();// delete pa; // 运行错误// unique_ptr unique_pb(pa); // 运行错误 A *pc = unique_pa.release(); delete pc; return 0;}
shared_ptr
必须使用shared_from_this
方法在对象内部如果想要获取指向该对象的shared_ptr
,不可以使用this
指针进行构造(理由见第3点),而必须使用shared_from_this
方法,以确保所有的shared_ptr
指向同一个控制块。
shared_from_this
情况:#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< get_shared_ptr(){ return shared_ptr (this);} ~A(){ cout< <<":析构函数"< pb = pa->get_shared_ptr(); // 运行错误 return 0;}pa(new A("shared_ptr"));// shared_ptr
shared_from_this
情况:#include#include using namespace std;class A:public enable_shared_from_this {public: string id; A(string id):id(id){ cout< <<":构造函数"< get_shared_ptr(){ return shared_from_this();} ~A(){ cout< <<":析构函数"< pa(new A("shared_ptr")); cout<<"use count = "< < pb = pa->get_shared_ptr(); cout<<"use count = "< <
运行结果:
shared_ptr:构造函数use count = 1use count = 2shared_ptr:析构函数
shared_from_this
方法当需要使用shared_from_this
方法时,应注意以下几点:
shared_from_this
方法在下面的代码中,shared_ptr<A> shared_pa(new A("shared_ptr"))
实际上执行了3个动作:首先调用enable_shared_from_this<A>
的构造函数;其次调用A
的构造函数;最后调用shared_ptr<A>
的构造函数。是第3个动作设置了enable_shared_from_this<A>
的weak_ptr
。
#include#include using namespace std;class A:public enable_shared_from_this {public: string id; A(string id):id(id){ cout< <<":构造函数"< pa = shared_from_this(); // 抛出异常 } ~A(){ cout< <<":析构函数"< shared_pa(new A("shared_ptr")); return 0;}
enable_shared_from_this<T>
在下面的代码中,子类B
并没有直接继承enable_shared_from_this
,而是使用dynamic_pointer_cast
进行了类型转换。
#include#include using namespace std;class A:public enable_shared_from_this {public: A(){ cout<<"调用构造函数A!"< getA(){ return shared_from_this(); }};class B:public A{public: B(){ cout<<"调用构造函数B!"< getB(){ return dynamic_pointer_cast (shared_from_this()); }};int main() { shared_ptr shared_pa(new B()); cout< < shared_pb = shared_pa->getA(); cout< < shared_pc = shared_pa->getB(); cout< <
运行结果:
调用构造函数A!调用构造函数B!123调用析构函数B!调用析构函数A!
weak_ptr.lock()
获取的shared_ptr
的有效性当通过weak_ptr.lock()
方法获取shared_ptr
时,必须判断该shared_ptr
是否有效,因为此时我们期望的shared_ptr
指向的对象也许已经被删除了。
#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< weak_pa; shared_ptr shared_pa(new A("shared_ptr")); weak_pa = shared_pa; cout< <
运行结果:
shared_ptr:构造函数0xfd11c8shared_ptr:析构函数0
make
函数初始化智能指针使用make_unique
和make_shared
初始化unique_ptr
和shared_ptr
具有如下优点(具体见):
当用new
创建一个对象的同时创建一个shared_ptr
时,这时会发生两次动态申请内存:一次是给使用new
申请的对象本身的,而另一次则是由shared_ptr
的构造函数引发的为资源管理对象分配的。
当使用make_shared
的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。
由于C++不保证函数实参求值顺序,若其中一个实参是用new
初始化的智能指针右值时,可能会因为异常而产生内存泄漏。
shared_ptr
指向动态数组时,必须使用自定义deleter
如果没有自定义deleter
,shared_ptr
在超出作用域时仅仅会释放指针所指向的对象的内存,即数组的第一个元素,数组的其他元素所在内存未被释放而造成内存泄露。
deleter
情况:#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< a(new A[2]{A("shared_ptr0"),A("shared_ptr1")}); return 0;}
运行结果:
shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr0:析构函数
deleter
情况:#include#include using namespace std;class A{public: string id; A(string id):id(id){ cout< <<":构造函数"< a(new A[2]{A("shared_ptr0"),A("shared_ptr1")}, [](A *a){ delete []a; }); return 0;}
运行结果:
shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr1:析构函数shared_ptr0:析构函数
shared_ptr
时应避免循环引用当shared_ptr
所指向的对象中包含shared_ptr
类型的成员变量时,应格外小心,防止由于循环引用而导致的内存泄漏。
下面代码展示了最简单的循环引用情况,shared_pa
所指向的对象的引用计数为2,当离开作用域时,shared_pa
本身被从栈上销毁,对象的引用计数减1,仍大于0,该对象未被释放。
所以,在设计类的时候,当不需要对象的所有权,也不想指定这个对象的生命周期时,可以考虑使用weak_ptr
代替shared_ptr
。
weak_ptr
情况:#include#include using namespace std;class A{public: string id; shared_ptr ptr; A(string id):id(id){ cout< <<":构造函数"< shared_pa(new A("shared_ptr")); shared_pa->ptr = shared_pa; return 0;}
运行结果:
shared_ptr:构造函数
weak_ptr
情况:#include#include using namespace std;class A{public: string id; weak_ptr ptr; A(string id):id(id){ cout< <<":构造函数"< shared_pa(new A("shared_ptr")); shared_pa->ptr = shared_pa; return 0;}
运行结果:
shared_ptr:构造函数shared_ptr:析构函数
share_ptr
的类型转换不能使用C++常用的转型函数share_ptr
的类型转换不能使用C++常用的转型函数,即static_cast,dynamic_cast,const_cast
,而要使用static_pointer_cast,dynamic_pointer_cast,const_pointer_cast
。
static_cast,dynamic_cast,const_cast
的功能是转换成对应的模版类型,即static_cast<T*>
其实是转换成类型为T
的指针。使用简单的C++转型函数是将share_ptr
对象转型为模版指针对象,导致转型的模版指针对象不能采用share_ptr
进行管理。因此,share_ptr
为了支持转型,提供了类似的转型函数即static_pointer_cast<T>
,从而使转型后仍然为shared_pointer
对象,仍然可以对指针进行管理。
#include#include using namespace std;class A{public: int a; virtual ~A(){}};class AA:public A{public: int aa;};class B{public: int b;};int main() { shared_ptr spa(new AA()); shared_ptr spb = spa; shared_ptr spc = dynamic_pointer_cast (spb); shared_ptr spd = spb; shared_ptr spe = static_pointer_cast (spd); shared_ptr spf = static_pointer_cast(spd);// shared_ptr spg = static_pointer_cast(spb); // 编译错误 cout< <
运行结果:
6
shared_ptr
没有保证共享对象的线程安全性shared_ptr
可以让你通过多个指针来共享资源,这些指针自然可以用于多线程。有些人想当然地认为用一个shared_ptr
来指向一个对象就一定是线程安全的,这是错误的。你仍然有责任使用一些同步原语来保证被shared_ptr
管理的共享对象是线程安全的。
参考链接
转载地址:http://thsws.baihongyu.com/