使用智能指针的注意事项
来源:互联网 发布:赌神2016网络电影 编辑:程序博客网 时间:2024/05/18 06:22
1. 使用unique_ptr
以替代auto_ptr
auto_ptr
是C++98标准库提供的一个智能指针,但已被C++11明确声明不再支持。auto_ptr
具有以下缺陷:
* auto_ptr
有拷贝语义,拷贝后源对象变得无效,这可能引发很严重的问题;而unique_ptr
则无拷贝语义,但提供了移动语义,这样的错误不再可能发生,因为很明显必须使用std::move()
进行转移。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { auto_ptr<A> auto_ap(new A("auto_ptr")),auto_bp; cout<<auto_ap.get()<<endl; auto_bp = auto_ap; cout<<auto_ap.get()<<endl; unique_ptr<A> unique_ap(new A("unique_ptr")),unique_bp; cout<<unique_ap.get()<<endl;// unique_bp = unique_ap; // 报错 unique_bp = move(unique_ap); cout<<unique_ap.get()<<endl; return 0;}
运行结果:
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 <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() {// auto_ptr<A[]> auto_ap(new A[1]{A("unique_ptr")}); // 报错 unique_ptr<A[]> unique_ap(new A[1]{A("unique_ptr")}); return 0;}
运行结果:
unique_ptr:构造函数unique_ptr:析构函数
auto_ptr
不可以自定义删除器deleter
,而unique_ptr
可以。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { unique_ptr<A,void(*)(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:析构函数
2. 尽量使用unique_ptr
而非shared_ptr
默认情况下,应使用unique_ptr
,理由如下:
- 若使用
unique_ptr
,当需要共享对象所有权时,依然可以将其转化为shared_ptr
,但反过来则不行。 - 使用
shared_ptr
需要消耗更多的资源,shared_ptr
需要维护一个指向动态内存对象的线程安全的引用计数器以及背后的一个控制块,这使它比unique_ptr
更加复杂。 - 共享对象所有权也许并非你的本意,但使用
shared_ptr
有可能造成其他程序员无意间通过赋值给另一个共享指针而修改了你共享出来的对象。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { unique_ptr<A> a(new A("unique_ptr")); shared_ptr<A> b = move(a);// a = move(b); // 报错// a.reset(b.get()); // 运行错误 cout<<a.get()<<endl; return 0;}
运行结果:
unique_ptr:构造函数0unique_ptr:析构函数
3. 谨慎使用裸指针
当需要裸指针与智能指针搭配使用时,需要避免如下操作:
- 使用裸指针初始化多个智能指针
- 对智能指针使用的裸指针执行
delete
操作
以上操作会导致程序再次尝试销毁已被销毁了的对象,进而造成程序崩溃。所以,当使用裸指针初始化智能指针后,应确保裸指针永远不应该被再次使用。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { A *pa = new A("ptrA"); unique_ptr<A> unique_pa(pa);// delete pa; // 运行错误 A *pb = new A("ptrB"); unique_ptr<A> unique_pb1(pb);// unique_ptr<A> unique_pb2(pb); // 运行错误 return 0;}
4. 不要使用静态分配对象的指针初始化智能指针
不要使用静态分配对象的指针初始化智能指针,否则,当智能指针本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};A a("全局变量");int main() { A b("局部变量");// unique_ptr<A> pa(&a); // 运行错误 unique_ptr<A> pa(&b); return 0;}
运行结果:
全局变量:构造函数局部变量:构造函数局部变量:析构函数局部变量:析构函数全局变量:析构函数
5. unique_ptr
可以作为函数返回值
尽管unique_ptr
无拷贝语义,但提供了移动语义,所以可作为函数返回值。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};unique_ptr<A> fun(){ cout<<"==>fun()"<<endl; unique_ptr<A> pa(new A("unique_ptr")); cout<<"<==fun()"<<endl; return pa;}int main() { cout<<"==>main()"<<endl; auto pa = fun(); cout<<"<==main()"<<endl; return 0;}
运行结果:
==>main()==>fun()unique_ptr:构造函数<==fun()<==main()unique_ptr:析构函数
6. 谨慎使用智能指针的get
与release
方法
当使用get
方法返回裸指针时,智能指针并没有释放指向对象的所有权,所以我们必须小心使用裸指针以避免程序崩溃。
但通过unique_ptr.release()
方法返回的裸指针,需要我们自己delete
删除对象,因为调用release
方法后,该unique_ptr
不再拥有对象的所有权。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { unique_ptr<A> unique_pa(new A("unique_ptr")); A *pa = unique_pa.get();// delete pa; // 运行错误// unique_ptr<A> unique_pb(pa); // 运行错误 A *pc = unique_pa.release(); delete pc; return 0;}
7. 在对象内部获取shared_ptr
必须使用shared_from_this
方法
在对象内部如果想要获取指向该对象的shared_ptr
,不可以使用this
指针进行构造(理由见第3点),而必须使用shared_from_this
方法,以确保所有的shared_ptr
指向同一个控制块。
- 未使用
shared_from_this
情况:
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} shared_ptr<A> get_shared_ptr(){return shared_ptr<A>(this);} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> pa(new A("shared_ptr"));// shared_ptr<A> pb = pa->get_shared_ptr(); // 运行错误 return 0;}
- 使用
shared_from_this
情况:
#include <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} shared_ptr<A> get_shared_ptr(){return shared_from_this();} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> pa(new A("shared_ptr")); cout<<"use count = "<<pa.use_count()<<endl; shared_ptr<A> pb = pa->get_shared_ptr(); cout<<"use count = "<<pa.use_count()<<endl; return 0;}
运行结果:
shared_ptr:构造函数use count = 1use count = 2shared_ptr:析构函数
8. 谨慎使用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 <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public: string id; A(string id):id(id){ cout<<id<<":构造函数"<<endl;// shared_ptr<A> pa = shared_from_this(); // 抛出异常 } ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> shared_pa(new A("shared_ptr")); return 0;}
- 在类的继承树中不能有2个或更多个
enable_shared_from_this<T>
在下面的代码中,子类B
并没有直接继承enable_shared_from_this
,而是使用dynamic_pointer_cast
进行了类型转换。
#include <iostream>#include <memory>using namespace std;class A:public enable_shared_from_this<A>{public: A(){ cout<<"调用构造函数A!"<<endl; } virtual ~A(){ cout<<"调用析构函数A!"<<endl; } shared_ptr<A> getA(){ return shared_from_this(); }};class B:public A{public: B(){ cout<<"调用构造函数B!"<<endl; } virtual ~B(){ cout<<"调用析构函数B!"<<endl; } shared_ptr<B> getB(){ return dynamic_pointer_cast<B>(shared_from_this()); }};int main() { shared_ptr<B> shared_pa(new B()); cout<<shared_pa.use_count()<<endl; shared_ptr<A> shared_pb = shared_pa->getA(); cout<<shared_pa.use_count()<<endl; shared_ptr<B> shared_pc = shared_pa->getB(); cout<<shared_pa.use_count()<<endl; return 0;}
运行结果:
调用构造函数A!调用构造函数B!123调用析构函数B!调用析构函数A!
9. 必须判断调用weak_ptr.lock()
获取的shared_ptr
的有效性
当通过weak_ptr.lock()
方法获取shared_ptr
时,必须判断该shared_ptr
是否有效,因为此时我们期望的shared_ptr
指向的对象也许已经被删除了。
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { weak_ptr<A> weak_pa; shared_ptr<A> shared_pa(new A("shared_ptr")); weak_pa = shared_pa; cout<<weak_pa.lock()<<endl; shared_pa.reset(); cout<<weak_pa.lock()<<endl; return 0;}
运行结果:
shared_ptr:构造函数0xfd11c8shared_ptr:析构函数0
10. 尽量使用make
函数初始化智能指针
使用make_unique
和make_shared
初始化unique_ptr
和shared_ptr
具有如下优点(具体见《智能指针之make_unique与make_shared》):
- 效率更高
当用new
创建一个对象的同时创建一个shared_ptr
时,这时会发生两次动态申请内存:一次是给使用new
申请的对象本身的,而另一次则是由shared_ptr
的构造函数引发的为资源管理对象分配的。
当使用make_shared
的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。
- 异常安全
由于C++不保证函数实参求值顺序,若其中一个实参是用new
初始化的智能指针右值时,可能会因为异常而产生内存泄漏。
11. 使用shared_ptr
指向动态数组时,必须使用自定义deleter
如果没有自定义deleter
,shared_ptr
在超出作用域时仅仅会释放指针所指向的对象的内存,即数组的第一个元素,数组的其他元素所在内存未被释放而造成内存泄露。
- 未自定义
deleter
情况:
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> a(new A[2]{A("shared_ptr0"),A("shared_ptr1")}); return 0;}
运行结果:
shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr0:析构函数
- 自定义
deleter
情况:
#include <iostream>#include <memory>using namespace std;class A{public: string id; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> a(new A[2]{A("shared_ptr0"),A("shared_ptr1")}, [](A *a){ delete []a; }); return 0;}
运行结果:
shared_ptr0:构造函数shared_ptr1:构造函数shared_ptr1:析构函数shared_ptr0:析构函数
12. 使用shared_ptr
时应避免循环引用
当shared_ptr
所指向的对象中包含shared_ptr
类型的成员变量时,应格外小心,防止由于循环引用而导致的内存泄漏。
下面代码展示了最简单的循环引用情况,shared_pa
所指向的对象的引用计数为2,当离开作用域时,shared_pa
本身被从栈上销毁,对象的引用计数减1,仍大于0,该对象未被释放。
所以,在设计类的时候,当不需要对象的所有权,也不想指定这个对象的生命周期时,可以考虑使用weak_ptr
代替shared_ptr
。
- 未使用
weak_ptr
情况:
#include <iostream>#include <memory>using namespace std;class A{public: string id; shared_ptr<A> ptr; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> shared_pa(new A("shared_ptr")); shared_pa->ptr = shared_pa; return 0;}
运行结果:
shared_ptr:构造函数
- 使用
weak_ptr
情况:
#include <iostream>#include <memory>using namespace std;class A{public: string id; weak_ptr<A> ptr; A(string id):id(id){cout<<id<<":构造函数"<<endl;} ~A(){cout<<id<<":析构函数"<<endl;}};int main() { shared_ptr<A> shared_pa(new A("shared_ptr")); shared_pa->ptr = shared_pa; return 0;}
运行结果:
shared_ptr:构造函数shared_ptr:析构函数
13. 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 <iostream>#include <memory>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<AA> spa(new AA()); shared_ptr<A> spb = spa; shared_ptr<AA> spc = dynamic_pointer_cast<AA>(spb); shared_ptr<void> spd = spb; shared_ptr<AA> spe = static_pointer_cast<AA>(spd); shared_ptr<B> spf = static_pointer_cast<B>(spd);// shared_ptr<B> spg = static_pointer_cast<B>(spb); // 编译错误 cout<<spa.use_count()<<endl; return 0;}
运行结果:
6
14. 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智能指针使用注意事项
- 使用智能指针的注意事项
- 智能指针使用注意事项
- 智能指针auto_ptr使用注意事项
- 智能指针auto_ptr使用注意事项
- C++智能指针使用注意事项
- 使用智能指针shared_ptr注意事项
- c++ shared_ptr智能指针使用注意事项
- c++ shared_ptr智能指针使用注意事项
- c++ shared_ptr智能指针使用注意事项
- 智能指针的使用
- 智能指针的使用
- 智能指针的使用
- 智能指针的使用
- 指针使用的注意事项
- 指针使用的注意事项
- 智能指针 _com_ptr_t的使用
- 使用智能指针的危险
- C++ 智能指针的使用
- C++基础(五)虚函数、重载、覆盖、隐藏
- ip分片+端口
- Linux进程管理(1):进程描述和进程创建
- ptrace的些许总结
- linux下静态库和动态库的区别
- 使用智能指针的注意事项
- 开源框架合集
- LINUX C中用define定义可变参数的宏
- copy_to_user和copy_from_user两个函数的分析
- pt_regs结构
- Linux VFS中write系统调用实现原理
- php过滤特殊字符
- wait_event_interruptible_out的返回值
- task _struct注释