使用make_shared时会发生什么?

27

我想知道这两行代码是否相同:

shared_ptr<int> sp(new int(1)); // double allocation?
shared_ptr<int> sp(make_shared<int>(1)); // just one allocation?

如果这是真的,有人能解释一下为什么第二行只有一个分配吗?


6
要明确的是,这不是对int进行双重分配。它只是两个单独的分配:一个用于int对象,另一个用于shared_ptr控制块。第二行只是一次同时分配int和控制块的单个分配。 - Joseph Mansfield
2
请看这里的第2点:http://herbsutter.com/2013/05/29/gotw-89-solution-smart-pointers/ - perreal
1
在第二种情况下,make_shared 分配了 int 和控制块,因此可以自由地一次性分配两者。在第一种情况下,您分配了 int,而 shared_ptr 的构造函数分配了控制块,没有办法将这两个分配合并。 - nwp
1
注意:make_shared 只执行一次分配内存的操作并不是保证,仅仅只是在实现中有可能做到。但如果你的实现没有执行这种优化,你应该向你的供应商提出投诉(至少 GCC、LLVM 和 MSVC 实现都可以做到,boost::make_shared 也可以)。 - Jonathan Wakely
这里有另一个不错的解释,解释了引擎内部发生了什么:http://stackoverflow.com/a/8646062/576911 - Howard Hinnant
3个回答

32
第一种情况并不执行双重分配,而是执行两个分配,一个用于"managed object"(被管理对象),另一个用于"control block"(控制块)的"shared_ptr"。
对于第二种情况,cppreference有一个很好的解释,为什么std::make_shared通常只执行一次内存分配,它说(以下是我的强调):

此函数通常使用单个内存分配为T对象和shared_ptr的控制块分配内存(这是标准中的非约束性要求)。相比之下,声明std::shared_ptr p(new T(Args...))至少执行两次内存分配,这可能会产生不必要的开销。

std::shared_ptr部分可以看到:
当通过调用std::make_shared或std::allocate_shared创建shared_ptr时,控制块和托管对象的内存将通过单个分配一起创建。托管对象在控制块的数据成员中原地构造。当通过其中一个shared_ptr构造函数创建shared_ptr时,必须分别分配托管对象和控制块。在这种情况下,控制块存储对托管对象的指针。
此“make_shared”说明与C++11草案标准一致,该标准在第20.7.2.2.6节“shared_ptr creation”中提到。
template<class T, class... Args> shared_ptr<T> make_shared(Args&&... args);
template<class T, class A, class... Args>
  shared_ptr<T> allocate_shared(const A& a, Args&&... args);

[...]

Remarks: Implementations should perform no more than one memory allocation. [ Note: This provides efficiency equivalent to an intrusive smart pointer. —end note ]

[ Note: These functions will typically allocate more memory than sizeof(T) to allow for internal bookkeeping structures such as the reference counts. —end note ]

Herb Sutter在GotW#89 Solution:Smart Pointers中更详细地解释了使用make_shared的优点,并指出了一些优点:

  • 它减少了分配开销
  • 它提高了局部性。
  • 避免了显式的new操作。
  • 避免了异常安全问题。

请注意,当使用std :: weak_ptr时,使用make_shared有一些缺点


4

cppreference std::shared_ptr实现注释 部分解释如下:

在典型的实现中,std::shared_ptr 仅持有两个指针:

  1. 指向托管对象的指针
  2. 指向控制块的指针

当通过调用 std::make_shared 或 std::allocate_shared 创建 shared_ptr 时,控制块和托管对象的内存都是通过单个分配创建的。托管对象将会原地构造在控制块的数据成员中。当通过 shared_ptr 构造函数创建 shared_ptr 时,托管对象和控制块必须分别进行分配。在这种情况下,控制块存储一个指向托管对象的指针。


1

还有一个潜在的细微错误:在 sp(new int) 中,您首先分配一个 int(其指针被赋予给 sp),然后 sp 本身必须分配一个控制块(其中包含计数器和删除器)。

现在,如果进行此最后一次分配时 sp 失败(内存不足),则会留下一个由堆分配的 int,其指针没有任何人持有,因此无法删除。(内存泄漏)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接