36

我正试图围绕 C++11 的新习语展开思考。

似乎至少使用 shared_ptr ,使用new T()和之间存在实质性区别make_shared<T>()

但是如何重置共享指针以指向某事物的新实例。以前,我通常会使用reset(new T())member。但是,这不会遇到与一开始不使用 make_shared() 相同的问题吗?(即它不允许 make_shared 分配对象,因此它被迫将引用计数放在单独的分配中,而不是与 T 本身放在相同的分配中?)

继续使用是否会更好:

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

或者,还有更好的方法?

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

4

2 回答 2

40

两者之间确实存在很大差异:

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,因此您必须自己编写一个。另一种情况是当您不希望将对象和计数器分配在单个内存块上时,因为指向对象的弱指针的存在将阻止整个块被释放,即使不再存在指向该对象的拥有指针.

于 2013-02-12T16:36:05.697 回答
6

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

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

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

赋值 frommake_shared<T>几乎是等价的,唯一的区别是删除旧的T和销毁临时的的相对顺序shared_ptr,这是不可观察的。

于 2013-02-12T16:41:13.450 回答