5

make_shared 为对象和引用计数器分配单个块。因此,使用这种技术有明显的性能优势。

我在 VS2012 中做了简单的实验,我正在寻找“证据”:

std::shared_ptr<Test> sp2 = std::make_shared<Test>();
std::shared_ptr<Test> sp(new Test());
// Test is a simple class with int 'm_value' member

调试时我在当地人看到这样的东西(有些行被删除)

-   sp2 shared_ptr {m_value=0 }  [make_shared] std::shared_ptr<Test>
+   _Ptr    0x01208dec {m_value=0 } Test *
+   _Rep    0x01208de0 make_shared  std::_Ref_count_base *

-   sp  shared_ptr {m_value=0 } [default] std::shared_ptr<Test>
+   _Ptr    0x01203c50 {m_value=0 } Test *
+   _Rep    0x01208d90 default  std::_Ref_count_base *

似乎sp2分配在0x01208de0(有一个 ref 计数器),然后在0x01208dec有一个 Test 对象。地点彼此非常接近。

在第二个版本中,我们有0x01208d90用于参考计数器,0x01203c50用于对象。这些位置相当遥远。

这是正确的输出吗?我理解正确吗?

4

3 回答 3

7

如果您阅读cppreference 的页面make_shared,他们会说:

此函数通过单个内存分配为T对象和的控制块分配内存。shared_ptr相反,该声明std::shared_ptr<T> p(new T(Args...))执行两次内存分配,这可能会产生不必要的开销。

所以这是预期的行为,你正确地解释了它。

当然,这是有道理的;如何shared_ptr控制您已经分配的对象的分配?使用make_shared,你让它负责分配对象,所以它可以在任何它想要的地方分配空间,它就在柜台旁边。

附录:正如 Pete Becker 在评论中指出的那样,标准的 §20.7.2.2.6/6 表示鼓励但不要求实现只执行一次分配。因此,不应依赖您观察到的这种行为,尽管可以肯定地说,如果您始终使用make_shared.

于 2013-02-02T20:06:55.217 回答
3

是的,显示的输出是正确的。

sp2, created through的情况下make_shared<>(),有一块连续的内存包含引用计数器和分配的对象。这就是两个地址接近的原因,这也是存在的主要原因之一make_shared<>()(只执行一次分配而不是两次分配)。

sp相反,在 的情况下,您通过单独分配对象new Test(),然后构造shared_ptr对象。的构造函数shared_ptr必须为引用计数器发出新的分配。由于这个原因,指向对象的地址和引用计数器的地址是相距甚远的。

于 2013-02-02T20:07:45.353 回答
1

这是正确的输出吗?

看起来像。

关键std::make_shared是性能 - 动态内存分配相对昂贵,并且仅仅为了保存引用计数器而进行额外分配可能相当浪费。因此,std::make_shared对象和计数器分配足够大的内存块,然后在该块中的正确位置初始化对象(使用放置 new )。

于 2013-02-02T20:12:42.687 回答