博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
使用智能指针的注意事项
阅读量:4298 次
发布时间:2019-05-27

本文共 9820 字,大约阅读时间需要 32 分钟。

1. 使用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_ptrunique_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:析构函数

2. 尽量使用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:析构函数

3. 谨慎使用裸指针

当需要裸指针与智能指针搭配使用时,需要避免如下操作:

  • 使用裸指针初始化多个智能指针
  • 对智能指针使用的裸指针执行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;}

4. 不要使用静态分配对象的指针初始化智能指针

不要使用静态分配对象的指针初始化智能指针,否则,当智能指针本身被撤销时,它将试图删除指向非动态分配对象的指针,导致未定义的行为。

#include 
#include
using namespace std;class A{public: string id; A(string id):id(id){
cout<
<<":构造函数"<
pa(&a); // 运行错误 unique_ptr
pa(&b); return 0;}

运行结果:

全局变量:构造函数局部变量:构造函数局部变量:析构函数局部变量:析构函数全局变量:析构函数

5. 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:析构函数

6. 谨慎使用智能指针的getrelease方法

当使用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;}

7. 在对象内部获取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<
<<":析构函数"<
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<
<<":构造函数"<
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:析构函数

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 
#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;}
  • 在类的继承树中不能有2个或更多个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!

9. 必须判断调用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

10. 尽量使用make函数初始化智能指针

使用make_uniquemake_shared初始化unique_ptrshared_ptr具有如下优点(具体见):

  • 效率更高

当用new创建一个对象的同时创建一个shared_ptr时,这时会发生两次动态申请内存:一次是给使用new申请的对象本身的,而另一次则是由shared_ptr的构造函数引发的为资源管理对象分配的。

当使用make_shared的时候,C++编译器只会一次性分配一个足够大的内存,用来保存这个资源管理者和这个新建对象。

  • 异常安全

由于C++不保证函数实参求值顺序,若其中一个实参是用new初始化的智能指针右值时,可能会因为异常而产生内存泄漏。

11. 使用shared_ptr指向动态数组时,必须使用自定义deleter

如果没有自定义deletershared_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:析构函数

12. 使用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:析构函数

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 
#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

14. shared_ptr没有保证共享对象的线程安全性

shared_ptr可以让你通过多个指针来共享资源,这些指针自然可以用于多线程。有些人想当然地认为用一个shared_ptr来指向一个对象就一定是线程安全的,这是错误的。你仍然有责任使用一些同步原语来保证被shared_ptr管理的共享对象是线程安全的。

参考链接

转载地址:http://thsws.baihongyu.com/

你可能感兴趣的文章
学习笔记_vnpy实战培训day02
查看>>
学习笔记_vnpy实战培训day03
查看>>
VNPY- VnTrader基本使用
查看>>
VNPY - CTA策略模块策略开发
查看>>
VNPY - 事件引擎
查看>>
MongoDB基本语法和操作入门
查看>>
学习笔记_vnpy实战培训day04_作业
查看>>
OCO订单(委托)
查看>>
学习笔记_vnpy实战培训day05
查看>>
学习笔记_vnpy实战培训day06
查看>>
Python super钻石继承
查看>>
回测引擎代码分析流程图
查看>>
Excel 如何制作时间轴
查看>>
股票网格交易策略
查看>>
matplotlib绘图跳过时间段的处理方案
查看>>
vnpy学习_04回测评价指标的缺陷
查看>>
ubuntu终端一次多条命令方法和区别
查看>>
python之偏函数
查看>>
vnpy学习_06回测结果可视化改进
查看>>
读书笔记_量化交易如何建立自己的算法交易01
查看>>