cpp之make_shared与shared_ptr

区别

先来看一段代码:

1
2
3
struct A;
std::shared_ptr<A> p1 = std::make_shared<A>();
std::shared_ptr<A> p2(new A);

他俩是有区别的,使用 make_shared 的方式只会申请一次内存,而使用 new 的方式要申请两次。
shared_ptr 内部有一个计数器,会维护指向当前对象的指针个数。

  1. 在使用 new 的方式申请的时候,先用 new 申请指向对象的内存,再申请 shared_ptr 中维护计数部分的内存,所以是两次申请。
  2. 用make_shared 的方式,是把指向对象部分和维护计数部分合在一起,一起申请,所以是一次申请。

那这两种有什么影响呢?

影响

1
2
3
4
void func(std::shared_ptr<Lhs> &lhs, std::shared_ptr<Rhs> &rhs) {
// do something
}
func(std::shared_ptr<Lhs>(new Lhs()), std::shared_ptr<Rhs>(new Rhs()));

C++ 允许在做参数运算的时候 打乱顺序,所以执行顺序可能为这样:

  1. new Lhs();
  2. new Rhs();
  3. shared_ptr 构造
  4. shared_ptr 构造

假设在步骤 2 中,因为内存不足抛出了异常。之后我们就丢失了 1 中申请的内存,因为 shared_ptr 还没有指向它,也就没有接受管理。有一种解决方案:

1
2
3
auto lhs = std::shared_ptr<Lhs>(new Lhs());
auto rhs = std::shared_ptr<Rhs>(new Rhs());
func(lhs, rhs);

这样不够优雅,所以诞生了 make_shared:

1
func(std::make_shared<Lhs>(), std::make_shared<Rhs>());

make_shared 的缺点

因为 make_shared 是申请(控制块 + 数据块)一整块内存,所以也是一起释放。weak_ptr 可以无限期地保持控制块的存活。
使用 new 申请的 shared_ptr 内存分布
使用 new 申请的 shared_ptr 内存分布
使用 make_shared 申请的 shared_ptr 内存分布
使用 make_shared 申请的 shared_ptr 内存分布
为什么 weak_ptr 的实例会使控制块保持活动状态?
必须有一种方法让 weak_ptr 来确定托管对象是否仍然有效(例如for lock)。他们通过检查 shared_ptr 拥有托管对象的数量来实现这一点,该托管对象存储在控制块中。结果是控制块处于活动状态,直到 shared_ptr 计数和 weak_ptr 计数都达到 00,才会释放整个控制块和数据块。