在使用传统指针的 C++ 编程中,我们经常遇到申请空间忘记释放或重复释放,甚至难以确定空间此时是否应该释放的问题。
智能指针可以实现资源的自动回收。它将普通的指针封装成一个对象。于是,在对象生命周期结束时,其所管理的内存也会被考虑释放。
C++ 提供三种智能指针:unique_ptr
用于管理所有权不可共享的对象,shared_ptr
用于管理可被共享的对象,weak_ptr
用于协助 shared_ptr
进行共享资源管理,但本身并不“参与”共享,只作为“旁观者”,收集资源的使用情况而不影响资源的共享状态。
滥用智能指针对代码的可读性会造成显著影响。因此,在使用智能指针前,需要酌情考虑,是否真的需要使用动态分配,是否能明确资源的所有权,为什么不采用非动态的值语义对象或普通引用。同时,还应当考虑到接口设计的兼容性和额外的性能开销。
unique_ptr
用于管理所有权互斥(exclusive ownership)的资源,即资源只能同时被一个实例拥有,只能被一个智能指针指向。
资源的创建通过 std::make_unique<T>()
完成。需要数组可直接将 T
改为 T[]
。指针操作的各种运算符都已被重载,可以很方便地使用。
unique_ptr
拒绝复制语义但允许移动语义。移动通过 std::move
完成,从 ptrA
到 ptrB
的移动操作会使 ptrB
拥有 ptrA
原先持有的资源,并导致 ptrA
成为空指针。这种特性可以用于方便而高效的实现工厂函数。
shared_ptr
用于管理所有权共享的资源。资源可以被多个实例同时拥有。这种共享所有权的管理通过引用计数实现。系统会为资源维护一个控制块,其中有引用计数等信息。当某个 shared_ptr
释放时,发现引用计数归零,则释放这块资源。
不同于 unique_ptr
,shared_ptr
无法直接管理数组,需要通过诸如手工指定自定义删除器等方式。但如此费劲,不妨考虑以容器代之。
shared_ptr
的拷贝构造、赋值运算均采用复制语义。std::move
仍然可以被用于实现所有权转移。
weak_ptr
类似 shared_ptr
,但它不会改变引用计数,也不能防止资源被释放。
weak_ptr::lock()
方法可以尝试通过一个 weak_ptr
创建 shared_ptr
。若资源已经被释放,则创建将会失败。
weak_ptr
的一个经典用途是解决只有 shared_ptr
时两个对象互相持有(作为成员的)指向对方的强引用,造成的循环引用问题。破环的一种手段,就是将其中一个 shared_ptr
以 weak_ptr
代之。
我们可以用类似的想法去构建一棵树。父节点持有子节点们的 shared_ptr
,而子节点持有父节点的 weak_ptr
。此时,若释放父节点,则它子树内的所有节点应当都被释放。