make_shared真的比新的更有效吗?

use*_*354 50 c++ clang shared-ptr make-shared libc++

我与尝试shared_ptr,并make_shared从C++ 11和编程的小玩具的例子来看看调用时什么是实际发生的事情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;
}
Run Code Online (Sandbox Code Playgroud)

现在看一下输出,请:

使用make_shared创建smart_ptr ...

构造函数make_shared

复制构造函数...

复制构造函数...

析构函数

析构函数

使用make_shared:done创建smart_ptr.

使用new创建smart_ptr ...

构造函数新

使用new:done创建smart_ptr.

析构函数

析构函数

似乎make_shared是两次调用复制构造函数.如果我为Object使用常规分配内存,则new不会发生,只Object构造一个.

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

相反,我不明白为什么这需要两次调用复制构造函数的成本Object.因此,我不相信在每种情况下make_shared都比使用分配更有效.我错了吗?好的,一个人可以实现一个移动构造函数,但我仍然不确定这是否比分配通过更有效.至少在每种情况下都不是.如果复制比为参考计数器分配内存便宜,那将是真的.但是- 内部参考计数器可以使用几种原始数据类型来实现,对吧?newObjectObjectnewObjectshared_ptr

make_shared尽管概述了复制开销,你能帮助并解释为什么在效率方面走的路?

Nic*_*las 37

作为基础设施,我使用llvm/clang 3.0以及XCode4中的llvm std c ++库.

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

要求:表达式:: new(pv)T(std :: forward(args)...),其中pv具有类型void*并且指向适合于保存类型T的对象的存储,应该很好地形成.A应为分配器(17.6.3.5).A的拷贝构造函数和析构函数不应抛出异常.

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
Run Code Online (Sandbox Code Playgroud)

我也把它移植到升压转换器的原始shared_ptrmake_shared,和我同样的事情,VC2010.

我建议提交一份错误报告,因为libc ++的行为已被破坏.

  • @NicolBolas:感谢针对libc ++的错误报告.我同意你的分析.这已在libc ++ public svn trunk中修复,不再调用复制构造函数. (13认同)

Ker*_* SB 32

你必须比较这两个版本:

std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Run Code Online (Sandbox Code Playgroud)

在你的代码中,第二个变量只是一个裸指针,而不是一个共享指针.


现在就肉了.make_shared (在实践中)更有效,因为它具有在一个单一的动态分配的实际对象一起分配参照控制块.相比之下,构造函数shared_ptr采用裸对象指针必须为引用计数分配另一个动态变量.权衡是make_shared(或其表兄allocate_shared)不允许您指定自定义删除器,因为分配是由分配器执行的.

(这不会影响对象本身的构造.从Object两个版本来看,两个版本之间没有区别.更有效的是共享指针本身,而不是托管对象.)

  • @ Ela782:是的,海湾合作委员会已经这么做了一段时间.这在20.7.2.2.6/6中的标准中明确建议. (3认同)

Eva*_*ran 6

因此,要记住的一件事是优化设置.在没有优化的情况下,测量性能,特别是关于c ++的性能是没有意义的.我不知道你是否真的用优化编译,所以我认为值得一提.

这就是说,你用这个测试测量是不是一种方式,make_shared是更有效的.简单地说,你正在测量错误的东西:-P.

这是交易.通常,当您创建共享指针时,它至少有2个数据成员(可能更多).一个用于指针,一个用于引用计数.这个引用计数在堆上分配(这样它就可以在shared_ptr不同的生命周期之间共享......毕竟这就是重点!)

因此,如果您正在创建一个类似于std::shared_ptr<Object> p2(new Object("foo"));至少有2个调用的对象new.一个用于Object和一个用于引用计数对象.

make_shared有一个选项(我不确定它必须),做一个new足够大的单一来保持指向的对象和引用计数在同一个连续的块中.有效地分配一个看起来像这样的对象(说明性的,而不是字面意思).

struct T {
    int reference_count;
    Object object;
};
Run Code Online (Sandbox Code Playgroud)

由于引用计数和对象的生命周期是捆绑在一起的(一个人比另一个人活得更久没有意义).整个块也可以delete同时进行.

所以效率是分配,而不是复制(我怀疑与优化相比,其他任何事情都是如此).

要明确的是,这就是提升所要说的 make_shared

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

除了方便和风格之外,这样的函数也是异常安全且相当快的,因为它可以对对象及其相应的控制块使用单个分配,从而消除了shared_ptr的构造开销的很大一部分.这消除了关于shared_ptr的主要效率投诉之一.