区别在于std::make_shared
执行一个堆分配,而调用std::shared_ptr
构造函数执行两个。
堆分配在哪里发生?
std::shared_ptr
管理两个实体:
- 控制块(存储元数据,例如引用计数,类型删除的删除器等)
- 被管理的对象
std::make_shared
对控制块和数据都需要的空间执行单个堆分配。在另一种情况下,new Obj("foo")
为托管数据调用堆分配,而std::shared_ptr
构造函数为控制块执行另一个分配。
欲了解更多信息,请查看执行音符在cppreference。
更新一:异常安全
注意(2019/08/30):自C ++ 17起,由于函数参数评估顺序的更改,这不是问题。具体来说,函数的每个参数都需要在评估其他参数之前完全执行。
由于OP似乎对事物的异常安全性感到疑惑,因此我更新了答案。
考虑这个例子,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
由于C ++允许子表达式求值的任意顺序,因此一种可能的顺序是:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
现在,假设我们在步骤2抛出了一个异常(例如,内存不足异常,Rhs
构造函数抛出了一些异常)。然后,我们将丢失在第1步分配的内存,因为没有任何机会清理它。问题的核心在于原始指针没有std::shared_ptr
立即传递给构造函数。
解决此问题的一种方法是将它们分开放置,以免发生这种任意排序。
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
当然,解决此问题的首选方法是使用std::make_shared
。
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
更新二:的缺点 std::make_shared
引用凯西的评论:
由于只有一种分配,因此在不再使用控制块之前,无法释放指针对象的内存。A weak_ptr
可以使控制块无限期地存活。
为什么weak_ptr
s的实例使控制块保持活动状态?
必须有一种方法weak_ptr
来确定s是否仍然有效(例如lock
)。他们通过检查shared_ptr
拥有被管理对象的s 的数量来做到这一点,该对象存储在控制块中。结果是控制块处于活动状态,直到shared_ptr
计数和weak_ptr
计数都达到0。
回到 std::make_shared
由于std::make_shared
对控制块和托管对象都进行了一次堆分配,因此无法独立为控制块和托管对象释放内存。我们必须等到可以释放控制块和被管理对象为止,碰巧直到没有shared_ptr
s或weak_ptr
s存活为止。
假设我们改为通过new
和shared_ptr
构造函数为控制块和托管对象执行了两次堆分配。然后,当不存在shared_ptr
s时,为托管对象释放内存(可能更早),而当不存在s时,为控制块释放内存(可能更晚)weak_ptr
。