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<":构造函数"<cout<":析构函数"<int main() {
auto_ptr auto_ap(new A("auto_ptr")),auto_bp;
cout<cout< unique_ap(new A("unique_ptr")),unique_bp;
cout<// unique_bp = unique_ap; // 报错
unique_bp = move(unique_ap);
cout<return 0;
}
运行结果:
auto_ptr:构造函数
0x6115d0
0
unique_ptr:构造函数
0x6115f0
0
unique_ptr:析构函数
auto_ptr:析构函数
auto_ptr
不可作为容器元素,unique_ptr
可以作为容器元素。因为auto_ptr
的拷贝和赋值具有破坏性,不满足容器要求:拷贝或赋值后,两个对象必须具有相同值。auto_ptr
不可指向动态数组,unique_ptr
可以指向动态数组。因为unique_ptr
有unique_ptr
重载版本,销毁动态对象时调用delete[]
。#include
#include
using namespace std;
class A{
public:
string id;
A(string id):id(id){cout<":构造函数"<cout<":析构函数"<int main() {
// auto_ptr 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<":构造函数"<cout<":析构函数"<int main() {
unique_ptrvoid(*)(A*)> 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<":构造函数"<cout<":析构函数"<int main() {
unique_ptr a(new A("unique_ptr"));
shared_ptr b = move(a);
// a = move(b); // 报错
// a.reset(b.get()); // 运行错误
cout<return 0;
}
运行结果:
unique_ptr:构造函数
0
unique_ptr:析构函数
当需要裸指针与智能指针搭配使用时,需要避免如下操作:
delete
操作以上操作会导致程序再次尝试销毁已被销毁了的对象,进而造成程序崩溃。所以,当使用裸指针初始化智能指针后,应确保裸指针永远不应该被再次使用。
#include
#include
using namespace std;
class A{
public:
string id;
A(string id):id(id){cout<":构造函数"<cout<":析构函数"<int main() {
A *pa = new A("ptrA");
unique_ptr 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<":构造函数"<cout<":析构函数"<"全局变量");
int main() {
A b("局部变量");
// unique_ptr 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<":构造函数"<cout<":析构函数"< fun(){
cout<<"==>fun()"< pa(new A("unique_ptr"));
cout<<"<==fun()"<return pa;
}
int main() {
cout<<"==>main()"<auto pa = fun();
cout<<"<==main()"<return 0;
}
运行结果:
==>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<":构造函数"<cout<":析构函数"<int main() {
unique_ptr 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<":构造函数"<shared_ptr get_shared_ptr(){return shared_ptr(this);}
~A(){cout<":析构函数"<int main() {
shared_ptr pa(new A("shared_ptr"));
// shared_ptr pb = pa->get_shared_ptr(); // 运行错误
return 0;
}
shared_from_this
情况:#include
#include
using namespace std;
class A:public enable_shared_from_this{
public:
string id;
A(string id):id(id){cout<":构造函数"<shared_ptr get_shared_ptr(){return shared_from_this();}
~A(){cout<":析构函数"<int main() {
shared_ptr pa(new A("shared_ptr"));
cout<<"use count = "<shared_ptr pb = pa->get_shared_ptr();
cout<<"use count = "<return 0;
}
运行结果:
shared_ptr:构造函数
use count = 1
use count = 2
shared_ptr:析构函数
shared_from_this
方法当需要使用shared_from_this
方法时,应注意以下几点:
shared_from_this
方法在下面的代码中,shared_ptr shared_pa(new A("shared_ptr"))
实际上执行了3个动作:首先调用enable_shared_from_this
的构造函数;其次调用A
的构造函数;最后调用shared_ptr
的构造函数。是第3个动作设置了enable_shared_from_this
的weak_ptr
。
#include
#include
using namespace std;
class A:public enable_shared_from_this{
public:
string id;
A(string id):id(id){
cout<":构造函数"<// shared_ptr pa = shared_from_this(); // 抛出异常
}
~A(){cout<":析构函数"<int main() {
shared_ptr shared_pa(new A("shared_ptr"));
return 0;
}
enable_shared_from_this
在下面的代码中,子类B
并没有直接继承enable_shared_from_this
,而是使用dynamic_pointer_cast
进行了类型转换。
#include
#include
using namespace std;
class A:public enable_shared_from_this{
public:
A(){
cout<<"调用构造函数A!"<virtual ~A(){
cout<<"调用析构函数A!"<shared_ptr getA(){
return shared_from_this();
}
};
class B:public A{
public:
B(){
cout<<"调用构造函数B!"<virtual ~B(){
cout<<"调用析构函数B!"<shared_ptr getB(){
return dynamic_pointer_cast(shared_from_this());
}
};
int main() {
shared_ptr shared_pa(new B());
cout<shared_ptr shared_pb = shared_pa->getA();
cout<shared_ptr shared_pc = shared_pa->getB();
cout<return 0;
}
运行结果:
调用构造函数A!
调用构造函数B!
1
2
3
调用析构函数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<":构造函数"<cout<":析构函数"<int main() {
weak_ptr weak_pa;
shared_ptr shared_pa(new A("shared_ptr"));
weak_pa = shared_pa;
cout<cout<return 0;
}
运行结果:
shared_ptr:构造函数
0xfd11c8
shared_ptr:析构函数
0
make
函数初始化智能指针使用make_unique
和make_shared
初始化unique_ptr
和shared_ptr
具有如下优点(具体见《智能指针之make_unique与make_shared》):
当用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<":构造函数"<cout<":析构函数"<int main() {
shared_ptr 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<":构造函数"<cout<":析构函数"<int main() {
shared_ptr 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<":构造函数"<cout<":析构函数"<int main() {
shared_ptr 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<":构造函数"<cout<":析构函数"<int main() {
shared_ptr 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
的指针。使用简单的C++转型函数是将share_ptr
对象转型为模版指针对象,导致转型的模版指针对象不能采用share_ptr
进行管理。因此,share_ptr
为了支持转型,提供了类似的转型函数即static_pointer_cast
,从而使转型后仍然为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<void> spd = spb;
shared_ptr spe = static_pointer_cast(spd);
shared_ptr spf = static_pointer_cast(spd);
// shared_ptr spg = static_pointer_cast(spb); // 编译错误
cout<return 0;
}
运行结果:
6
shared_ptr
没有保证共享对象的线程安全性shared_ptr
可以让你通过多个指针来共享资源,这些指针自然可以用于多线程。有些人想当然地认为用一个shared_ptr
来指向一个对象就一定是线程安全的,这是错误的。你仍然有责任使用一些同步原语来保证被shared_ptr
管理的共享对象是线程安全的。
参考链接
使用 C++11 智能指针时要避开的 10 大错误
auto_ptr 代码及缺陷
C++11智能指针之unique_ptr
静态或者全局智能指针使用的注意几点
为什么函数可以返回unique_ptr
What is the usefulness of enable_shared_from_this?
Shared_from_this 几个值得注意的地方
c++ shared_ptr智能指针使用注意事项