make_shared真的比新的更有效率吗?

我正在用C ++ 11试验shared_ptrmake_shared ,并编写了一个小玩具的例子来看看调用make_shared时实际发生了什么。 作为基础结构,我使用了llvm / clang 3.0以及XCode4中的llvm std c ++库。

 class Object { public: Object(const string& str) { cout << "Constructor " << str << endl; } Object() { cout << "Default constructor" << endl; } ~Object() { cout << "Destructor" << endl; } Object(const Object& rhs) { cout << "Copy constructor..." << endl; } }; void make_shared_example() { cout << "Create smart_ptr using make_shared..." << endl; auto ptr_res1 = make_shared<Object>("make_shared"); cout << "Create smart_ptr using make_shared: done." << endl; cout << "Create smart_ptr using new..." << endl; shared_ptr<Object> ptr_res2(new Object("new")); cout << "Create smart_ptr using new: done." << endl; } 

现在看看输出结果,请:

使用make_shared创buildsmart_ptr …

构造函数make_shared

复制构造函数…

复制构造函数…

析构函数

析构函数

使用make_shared创buildsmart_ptr:完成。

使用新build…创buildsmart_ptr …

构造函数new

使用new:done创buildsmart_ptr。

析构函数

析构函数

看来make_shared正在调用复制构造函数两次。 如果我使用普通的new分配一个Object内存,这不会发生,只有一个Object被构造。

我想知道的是以下几点。 我听说make_shared应该比使用new ( 1,2 )更有效率。 一个原因是因为make_shared将引用计数与要在同一块内存中pipe理的对象一起分配。 好的,我明白了。 这当然比两个单独的分配操作更有效率。

相反,我不明白为什么这必须与两个Object的复制构造函数调用的代价。 正因为如此,我不相信make_shared更有效率比在每一个情况下使用new分配。 我错了吗? 好了,可以实现Object的移动构造函数,但是我不确定这是否比通过new分配Object更高效。 至less不是每一种情况。 如果复制Object比为参考计数器分配内存要便宜,那将是事实。 但是shared_ptr -internal引用计数器可以用一些原始数据types来实现,对吗?

您能否帮助解释为什么make_shared是效率方面的最好方式?

作为基础结构,我使用了llvm / clang 3.0以及XCode4中的llvm std c ++库。

那么这似乎是你的问题。 C ++ 11标准在第20.7.2.2.6节中规定了make_shared<T> (和allocate_shared<T> )的以下要求:

需要:expression式:: new(pv)T(std :: forward(args)…),其中pv的types为void *,并且指向适合容纳Ttypes对象的存储。 A应该是一个分配器(17.6.3.5)。 A的拷贝构造函数和析构函数不得抛出exception。

T不需要是可复制的。 事实上, T甚至不需要非安置 – 新的可施工。 只需要在原地进行施工。 这意味着make_shared<T>唯一可以用T来做的事情就是new

所以你得到的结果是不符合标准的。 LLVM的libc ++在这方面被打破了。 提交一个错误报告。

作为参考,下面是我将代码放入VC2010时发生的情况:

 Create smart_ptr using make_shared... Constructor make_shared Create smart_ptr using make_shared: done. Create smart_ptr using new... Constructor new Create smart_ptr using new: done. Destructor Destructor 

我也把它移植到Boost的原始shared_ptrmake_shared ,我得到了和VC2010一样的东西。

我build议提交一个错误报告,因为libc ++的行为被破坏了。

你必须比较这两个版本:

 std::shared_ptr<Object> p1 = std::make_shared<Object>("foo"); std::shared_ptr<Object> p2(new Object("foo")); 

在你的代码中,第二个variables只是一个裸指针,根本不是一个共享指针。


现在在肉上。 make_shared (在实践中)更高效,因为它将参考控制块与实际对象一起分配在一个dynamic分配中。 相比之下,使用裸对象指针的shared_ptr的构造函数必须为引用计数分配另一个dynamicvariables。 权衡是make_shared (或者它的堂兄allocate_shared )不允许你指定一个定制的删除器,因为这个分配器是由分配器执行的。

(这并不影响对象本身的构造,从Object的angular度来看,两个版本没有区别,更有效的是共享指针本身,而不是托pipe对象)。

所以有一点要记住是你的优化设置。 如果没有启用优化,衡量性能,特别是关于c ++的性能是没有意义的 。 我不知道你是否事实上编译了优化,所以我认为这是值得一提的。

也就是说,你用这个testing测量的结果并不是 make_shared更有效率的方法。 简而言之,你正在测量错误的东西:-P。

这是交易。 通常,当你创build共享指针时,它至less有2个数据成员(可能更多)。 一个用于指针,另一个用于引用计数。 这个引用计数被分配在堆上(这样它可以在shared_ptr之间共享不同的生命期,毕竟这是最重要的!)

所以如果你用std::shared_ptr<Object> p2(new Object("foo"));创build一个对象, 至less有2个电话给new 。 一个用于Object ,另一个用于引用计数对象。

make_shared有选项(我不知道它必须),做一个new的大到足以容纳指向的对象和引用计数在同一个连续的块。 有效地分配一个看起来像这样的对象(说明性的,不是字面意思)。

 struct T { int reference_count; Object object; }; 

由于引用计数和对象的生命期是连在一起的(对于一个人来说寿命比另一个长是没有意义的)。 这个整块也可以同时delete

所以效率是在分配,而不是在复制(我怀疑与优化比任何其他)。

要清楚的是,这是关于make_shared

http://www.boost.org/doc/libs/1_43_0/libs/smart_ptr/make_shared.html

除了方便和风格之外,这样的函数也是exception安全的,速度也相当快,因为​​它可以为对象和相应的控制块使用一个单独的分配,消除了shared_ptr构造开销的重要部分。 这消除了关于shared_ptr的主要效率投诉之一。

你不应该在那里得到任何额外的副本。 输出应该是:

 Create smart_ptr using make_shared... Constructor make_shared Create smart_ptr using make_shared: done. Create smart_ptr using new... Constructor new Create smart_ptr using new: done. Destructor 

我不知道你为什么得到额外的副本。 (虽然我看到你得到一个'破坏者'太多,所以你用来获得你的输出的代码必须不同于你发布的代码)

make_shared效率更高,因为它只能使用一个dynamic分配来实现,而不是两个,因为每个共享对象只需要一个指针的内存就less了一些。

编辑:我没有检查与Xcode 4.2,但与Xcode 4.3我得到正确的输出我上面显示,而不是在问题中显示的不正确的输出。