std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
许多google和stackoverflow帖子都在这上面,但我无法理解为什么make_shared
比直接使用shared_ptr
更有效。
有人可以解释我一步一步创建的对象序列和两者所做的操作,这样我就能理解make_shared
是如何有效的。我在上面给出了一个例子供参考。
区别在于std::make_shared
执行一个堆分配,而调用std::shared_ptr
构造函数执行两个。
std::shared_ptr
管理两个实体:
std::make_shared
对控制块和数据所需的空间执行单个堆分配计算。在另一种情况下,new Obj("foo")
为托管数据调用堆分配,std::shared_ptr
构造函数为控制块执行另一个。
有关详细信息,请查看cppreference的实施说明。
由于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
的缺点引用Casey的评论:
由于只有一个分配,因此在控制块不再使用之前,不能释放指针的内存。
weak_ptr
可以无限期地保持控制块的活着。
weak_ptr
s keep the control block alive?必须有一种方法让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存活时,我们释放托管对象的内存(可能更早),并且当没有weak_ptr
s存活时释放控制块的内存(可能更晚)。
共享指针管理对象本身,以及包含引用计数和其他管家数据的小对象。 make_shared
可以分配一块内存来容纳这两种内存;从指向已分配对象的指针构造共享指针将需要分配第二个块来存储引用计数。
除了这个效率之外,使用make_shared
意味着您根本不需要处理new
和原始指针,从而提供更好的异常安全性 - 在分配对象之后但在将其分配给智能指针之前不可能抛出异常。
还有另一种情况,除了已经提到的两种可能性之外,如果你需要调用非公共构造函数(受保护或私有),make_shared可能无法访问它,而具有new的变体可以正常工作。
class A
{
public:
A(): val(0){}
std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
// Invalid because make_shared needs to call A(int) **internally**
std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
// Works fine because A(int) is called explicitly
private:
int val;
A(int v): val(v){}
};
如果在shared_ptr控制的对象上需要特殊的内存对齐,则不能依赖make_shared,但我认为这是不使用它的唯一理由。
Shared_ptr
:执行两次堆分配
Make_shared
:仅执行一次堆分配
关于分配的效率和关注时间,我在下面进行了这个简单的测试,我通过这两种方式创建了许多实例(一次一个):
for (int k = 0 ; k < 30000000; ++k)
{
// took more time than using new
std::shared_ptr<int> foo = std::make_shared<int> (10);
// was faster than using make_shared
std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}
问题是,与使用new相比,使用make_shared需要花费两倍的时间。因此,使用new有两个堆分配,而不是使用make_shared。也许这是一个愚蠢的测试,但它不表明使用make_shared需要比使用new更多的时间吗?当然,我说的只是时间。
我看到std :: make_shared存在一个问题,它不支持私有/受保护的构造函数