是使用shared_ptr.reset还是operator =?

时间:2013-02-12 16:07:42

标签: c++ c++11 shared-ptr

我正试图围绕C ++ 11的新习语。

似乎至少使用shared_ptr,使用new T()make_shared<T>()之间存在实质性差异。

但是重置共享指针以指向某个新实例的内容。以前,我通常会使用reset(new T())成员。但是,这不是因为没有首先使用make_shared()的问题吗? (即它不允许make_shared分配对象,因此它被强制将ref计数放在一个单独的分配中而不是与T本身相同的分配中?)

是否可以更好地继续使用:

   mysharedptr = make_shared<T>(args...);

或者有更好的方法吗?

并且不应该像make_shared那样重置提供参数的变量转发,这样就可以编写mysharedptr.reset(args ...);?

2 个答案:

答案 0 :(得分:34)

确实存在很大差异:

shared_ptr<T> sp(new T());

shared_ptr<T> sp = make_shared<T>();

第一个版本为T对象执行分配,然后执行单独分配以创建引用计数器。第二个版本为对象和引用计数器执行一次单独分配,将它们放在连续的内存区域中,从而减少内存开销。

此外,某些实现能够在make_shared<>的情况下执行进一步的空间优化(请参阅&#34;我们知道您居住的地方&#34;由MS实施的优化完成)。< / p>

但是,这不是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())将对计数器和对象执行单独的分配,就像构建新的shared_ptr<>将执行将原始指针作为参数传递时的单独分配。因此,最好使用make_shared<>进行分配(甚至可以使用reset(make_shared<T>())等语句。

reset()是否应该支持可变参数列表,这可能更像是一种不太适合StackOverflow的公开讨论。

(*)有一些情况仍然需要它。例如,C ++标准库缺少make_unique<>的相应unique_ptr函数,因此您必须自己编写一个。{1}}。另一种情况是,当您不希望在单个内存块上分配对象和计数器时,因为存在指向对象的弱指针将阻止整个块被释放,即使没有存在更多拥有该对象的指针。

答案 1 :(得分:6)

正确,reset(new T...)遭遇shared_ptr(new T...)的所有问题;它将导致双重分配并且也是非异常安全的(除非bad_alloc内发生reset,否则泄漏的可能性不大。

reset被记录为等同于shared_ptr<T>(ptr).swap(*this),因此您也可以写:

make_shared<T>(args...).swap(mysharedptr);

来自make_shared<T>的作业几乎是等同的,唯一的区别是删除旧T的相对顺序和临时shared_ptr的破坏,这是不可观察的。< / p>