我正试图围绕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 ...);?
答案 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>