Understanding C++11: Smart Pointer
程序的内存按类型来划分,分为三种:
- 静态内存(Static Memory)保存局部static对象、类static数据成员、定义在任何函数之外的变量
- 栈内存(Stack Memory)保存定义在函数内的非static对象
- 堆(Heap Memory)用来存储动态分配的对象
如果按照生命周期来看的话,则可以看成两类:
- 静态内存和栈内存中的对象由编译器控制
- 堆内存中的对象生存期由程序员控制
Raw Pointer
C++通过new
和delete
来生成和销毁动态内容,并使用原生指针(raw pointer)来访问内存,但其缺陷有:
- 容易造成资源泄露;
- 重复销毁会导致未定义行为;
- 无法识别指针指向的是单一对象,还是数组;
Smart Pointer
C++ 提供了智能指针,它们为模板类,适用于任何类型
C++11里,一共提供了三个类:
shared_ptr
,允许多个指针指向同一对象unique_ptr
,”独占”所指向的对象weak_ptr
,弱引用指向shared_ptr
所管理的对象
被废弃的auto_ptr
auto_ptr
于C++ 98引入,其保存一个指向对象的指针(不能指向动态分配的数组),当auto_ptr
对象超出了作用域或另外撤销时,就会回收期所指向的动态分配对象。
auto_ptr
对象的复制和赋值是破坏性操作,在拷贝和赋值之后,原本的auto_ptr
会交出拥有权,而不是拷贝给新的auto_ptr
。
因此,auto_ptr
不满足STL容器对其元素的要求,绝不要把auto_ptr
作为标准容器的元素。同时,也不能作为函数返回值。
C++11发布以后,auto_ptr
已经被推荐不再使用了。
取而代之的unique_ptr
unique_ptr
主要的用途在于帮助避免资源泄露,比如在对象初始化期间因抛出异常而造成资源泄露,再比如虽然在函数的末尾有进行delete
,但是有两类场景容易导致疏漏:
- 函数中间有return语句,因此跳过了末尾的
delete
- 函数中间有代码抛出了异常,未进行捕获,直接抛出,导致跳过
delete
解决上述两类场景问题的法门之一,就是定义一个局部变量,其会在函数结束时被自动销毁,不论是正常结束,还是异常结束。如果这个变量是unique_ptr
,则可以在销毁时,执行自定义的逻辑,释放相关资源。
在使用unique_ptr
时,需要注意:
- 必须直接初始化,
- 不一定拥有对象,可以是empty,赋以
nullptr
,或者调用reset()
- 不可以执行copy或assign,可以使用move语义
上述第三条存在一个例外,即return
语句不需要std::move
,因为编译器会尝试加上。
对于数组类型,unique_ptr
提供了额外的支持,提供操作符[],但是其也存在制约,不再提供操作符*
和->
,不接受一个派生类型的array作为初值。
主角的shared_ptr
shared_ptr
是最为常用的智能指针,其允许多个指针指向同一个对象,其作用有:
- 可以赋值、拷贝、比较
- 可使用操作符
*
和->
对于数组类型,和unique_ptr
不同,shared_ptr
不提供[]
运算符。同时,因为shared_ptr
提供的default deleter调用的是delete
,而不是delete[]
,因此无法正确释放数组。不过,对此情况可以有两种途径解决:
- 可以通过定义自己的deleter,通过传递一个函数或lambda,执行
delete[]
- 使用
std::default_delete
作为deleter
助攻的weak_ptr
weak_ptr
的用途主要是辅助,常用的两个使用场景有:
- 破解 cyclic reference,两个对象使用
shared_ptr
互相指向对方 - reference 的寿命比所指对象的寿命要长
其也存在一些制约,比如:
- 在default和copy构造函数之外,class
weak_ptr
只提供“接受一个shared_ptr
”的构造函数 - 不能够使用操作符
*
和->
访问所指向的对象