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对象。地点非常接近。
在第二个版本中,我们对于ref计数器有 0x01208d90 ,对于对象有 0x01203c50 。那些地方相当遥远。
这是正确的输出吗?我能理解这一点吗?
答案 0 :(得分:5)
如果您阅读cppreference's page for 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
,你可以安全地说你没有什么可失去的东西可以获得。
答案 1 :(得分:1)
是的,显示的输出是正确的。
在通过sp2
创建的make_shared<>()
的情况下,有一个连续内存块包含引用计数器和分配的对象。这就是两个地址接近的原因,这也是make_shared<>()
存在的主要原因之一(仅执行一个分配而不是两个)。
在sp
的情况下,您可以通过new Test()
单独分配对象,然后构造shared_ptr
对象。 shared_ptr
的构造函数必须为引用计数器发出新的分配。因此,尖头物体的地址和参考计数器的地址很远。
答案 2 :(得分:0)
这是正确的输出吗?
看起来像。
std::make_shared
的重点是性能 - 动态内存分配相对昂贵,仅仅为了保存引用计数器而进行额外分配可能相当浪费。因此,std::make_shared
为对象和计数器分配足够大的内存块,然后在该板中正确的位置初始化对象(使用placement new)