两者之间确实存在很大差异:
shared_ptr<T> sp(new T());
和:
shared_ptr<T> sp = make_shared<T>();
第一个版本为T
对象执行分配,然后执行单独的分配以创建引用计数器。第二个版本对对象和引用计数器执行一次分配,将它们放置在连续的内存区域中,从而减少内存开销。
此外,一些实现能够在以下情况下执行进一步的空间优化make_shared<>
(请参阅 MS 的实现完成的“我们知道你住在哪里”优化)。
然而,这并不是存在的唯一原因make_shared<>
。在某些情况下,基于显式的版本new T()
不是异常安全的,尤其是在调用接受shared_ptr
.
void f(shared_ptr<T> sp1, shared_ptr<T> sp2);
...
f(shared_ptr<T>(new T()), shared_ptr<T>(new T()))
在这里,编译器可以计算第一个new T()
表达式,然后计算第二个new T()
表达式,然后构造相应的shared_ptr<>
对象。但是,如果第二个分配在第一个分配的对象绑定到它之前导致异常shared_ptr<>
怎么办?它会被泄露。使用make_shared<>()
,这是不可能的:
f(make_shared<T>(), make_shared<T>())
因为分配的对象绑定到shared_ptr<>
每个函数调用中的相应对象make_shared<>()
,所以这个调用是异常安全的。new
这也是为什么除非你真的知道自己在做什么,否则永远不应该使用裸体的另一个原因。(*)
考虑到您对 的评论reset()
,您正确地观察到它将为计数器和对象执行单独的分配,就像当原始指针作为参数传递时reset(new T())
new 的构造将执行单独的分配一样。shared_ptr<>
因此,赋值 usingmake_shared<>
是更可取的(甚至是诸如 之类的语句reset(make_shared<T>())
)。
无论是否reset()
应该支持可变参数列表,这可能更像是一种开放式讨论,StackOverflow 不适合。
(*) 有一些情况仍然需要它。例如,C++ 标准库缺少对应的make_unique<>
函数unique_ptr
,因此您必须自己编写一个。另一种情况是当您不希望将对象和计数器分配在单个内存块上时,因为指向对象的弱指针的存在将阻止整个块被释放,即使不再存在指向该对象的拥有指针.