区别
先来看一段代码:
1 | struct A; |
他俩是有区别的,使用 make_shared 的方式只会申请一次内存,而使用 new 的方式要申请两次。
shared_ptr 内部有一个计数器,会维护指向当前对象的指针个数。
- 在使用 new 的方式申请的时候,先用 new 申请指向对象的内存,再申请 shared_ptr 中维护计数部分的内存,所以是两次申请。
- 用make_shared 的方式,是把指向对象部分和维护计数部分合在一起,一起申请,所以是一次申请。
那这两种有什么影响呢?
影响
1 | void func(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs) { |
C++ 允许在做参数运算的时候 打乱顺序,所以执行顺序可能为这样:
- new Lhs();
- new Rhs();
- shared_ptr
构造 - shared_ptr
构造
假设在步骤 2 中,因为内存不足抛出了异常。之后我们就丢失了 1 中申请的内存,因为 shared_ptr 还没有指向它,也就没有接受管理。有一种解决方案:
1 | auto lhs = std::shared_ptr<Lhs>(new Lhs()); |
这样不够优雅,所以诞生了 make_shared:
1 | func(std::make_shared<Lhs>(), std::make_shared<Rhs>()); |
make_shared 的缺点
因为 make_shared 是申请(控制块 + 数据块)一整块内存,所以也是一起释放。weak_ptr 可以无限期地保持控制块的存活。
使用 new 申请的 shared_ptr 内存分布
使用 make_shared 申请的 shared_ptr 内存分布
为什么 weak_ptr 的实例会使控制块保持活动状态?
必须有一种方法让 weak_ptr 来确定托管对象是否仍然有效(例如for lock)。他们通过检查 shared_ptr 拥有托管对象的数量来实现这一点,该托管对象存储在控制块中。结果是控制块处于活动状态,直到 shared_ptr 计数和 weak_ptr 计数都达到 ,才会释放整个控制块和数据块。