C++智能指针
# 三种智能指针简介
参考 https://www.cnblogs.com/wang1994/p/10765974.html (opens new window)
shared_ptr shared_ptr的多个对象指向同一个指针(大多是new出来的空间指针),该指针使用引用计数,每使用一次,内部计数器加1,每析构一次,内部的引用计数器减1,减为0的时候,自动删除指向的堆内存。
unique_ptr “唯一”拥有所指对象,同一时刻只能有一个unique_ptr指向给定象(禁止使用拷贝语义,只能用移动语义将其移动)。对比原始指针,也是利用了RAII的特性。用户可以定义delete操作。可以通过move、release等转移所有权。
weak_ptr 当两个
shared_ptr
互相引用时会引起死锁,导致都不释放。为了解决这种情况,引入weak_ptr
配合shared_ptr
,是弱引用,相对于shared_ptr
强引用来说的。看似就像一个观察者,观测资源的使用情况。weak_ptr
可以从一个shared_ptr
获取另一个weak_ptr
来构造,获取资源的观察权 。 并不会引起计数加的情况,成员有use_count(),查看资源的引用计数,expired(),判断是否指向的资源被释放, 当返回为true的时候,这个资源的引用计数为0,相当于被释放,反之就没有被释放掉。lock(),返回当前分享指 针,计数器并加1.{ sp1 = make_shared<A_t> A; sp2 = make_shared<A_t> B; }
1
2
3
4如图,左边是A中有一个
shared_ptr
指向B, B中有一个shared_ptr
指向A。A的引用计数为2(sp1、sp4),B的引用计数也为2(sp2、sp3)。sp1和sp3析构完,A和B的引用都还剩1,无法释放。右边A中有一个
shared_ptr
指向B,B中有一个weak_ptr
指向A。A的引用计数为1(sp1),B的引用计数为2(sp2、sp3)。当sp1和sp3析构时,A的引用计数减1后释放,B的引用计数减2后释放。weak_ptr使用时,不能直接访问成员,需要通过lock函数得到一个shared_ptr,再通过该shared_ptr访问成员。因为直接使用weak_ptr可能会在过程中对象析构,而通过lock得到shared_ptr就不会出现这种情况。
# shared_ptr 内部实现
参考: https://blog.csdn.net/hanzhen7541/article/details/105473228 (opens new window)
shared_ptr内部维护引用计数 *count,指向 count, 还有一个指向实际资源的地址 *resource。
构造函数, 如果给了资源了,则
*count=1
, 没给则*count=0
拷贝构造函数,
atomic *count++
赋值运算符 = , 原来的
atomic *count--
, 新的*count++
对引用计数的加减操作是原子操作(线程安全),对实际资源并无原子操作保护。
# 类中含有 shared_ptr 时的拷贝构造函数
当类中含有智能指针时,编译器为其自动生成的拷贝构造函数会增加引用计数,因为:
自动生成的拷贝构造函数将为每个成员变量使用拷贝构造函数,或对内置类型进行逐位复制。
如果用memcpy对 shared_ptr 进行拷贝,则会导致实际的引用多了1, 而引用计数没有增加。这种情况在析构时不会出错,但是在引用时可能会出错。
# 尽量用make_shared对shared_ptr初始化
参考: https://www.jianshu.com/p/03eea8262c11 (opens new window)
用new初始化,两次内存分配:
std::shared_ptr<Widget> spw(new Widget);
用make_shared初始化,一次内存分配, 智能指针和对象存在同一块区域:
auto spw = std::make_shared<Widget>();
make_shared缺点:
构造函数是保护或私有时,无法使用 make_shared。
对象的内存可能无法及时回收 make_shared 只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr 会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr 离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题.
# 注意避坑
参考: 使用智能指针的17个原则:https://www.cnblogs.com/lidabo/p/3911440.html (opens new window)
weak_ptr 在循环中会严重降低效率。
不要把this指针给shared_ptr
# 避免内存圈养(shared_ptr循环依赖导致内存不释放)
参考: https://github.com/xhawk18/noshared_ptr
shared_ptr大量使用时,很可能导致循环依赖从而使得内存无法释放。为了避免这种情况,尽可能明确shared_ptr的归属,防止滥用。上面这个noshared_ptr就是一个很好的解决方案,它相当于是unique_ptr和unique_ref_ptr, 保证了拥有者只有一个,并且观测者ref_ptr在其作用域内临时lock()出的ptr只在作用域内有效,出了作用域后就无效了。
个人觉得上面的实现对于使用者来说内部原理不容易理解,可能会出错。可以考虑用三种类型来实现上述思想: unique_ptr、 unique_weak_ptr、 unique_lock_ptr 。其中:
- unique_ptr可以创建unique_weak_ptr;
- unique_weak_ptr可以lock出unique_lock_ptr; unique_lock_ptr不能被move、copy