make_shared真的比新的更有效吗?

时间:2012-02-15 22:12:34

标签: c++ shared-ptr clang libc++ 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创建smart_ptr ...

     

构造函数make_shared

     

复制构造函数......

     

复制构造函数......

     

析构

     

析构

     

使用make_shared创建smart_ptr:done。

     

使用new ...

创建smart_ptr      

构造函数new

     

使用new:done。

创建smart_ptr      

析构

     

析构

make_shared似乎正在调用复制构造函数两次。如果我使用常规Objectnew分配内存,则不会发生这种情况,只会构建一个Object

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

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

你能帮助并解释为什么make_shared是效率方面的方法,尽管概述了副本开销?

4 个答案:

答案 0 :(得分: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就位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 就位。

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

作为参考,这是我将代码带入VC2010时发生的事情:

shared_ptr

我还将它移植到Boost的原始make_shared和{{1}},我和VC2010一样。

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

答案 1 :(得分:32)

您必须比较这两个版本:

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

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


现在开始了。 make_shared (实际上)效率更高,因为它在一个动态分配中将参考控制块与实际对象一起分配。相比之下,采用裸对象指针的shared_ptr构造函数必须为引用计数分配另一个动态变量。权衡是make_shared(或其堂兄allocate_shared)不允许您指定自定义删除器,因为分配是由分配器执行的。

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

答案 2 :(得分:6)

因此要记住的一件事是优化设置。测量性能,尤其是关于c ++的性能无意义,未启用优化。我不知道你是否真的用优化编译,所以我认为值得一提。

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

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

因此,如果您要使用std::shared_ptr<Object> p2(new Object("foo"));之类的内容创建对象,则new至少会有 2 次调用。一个用于Object,一个用于引用计数对象。

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

struct T {
    int reference_count;
    Object object;
};

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

因此,效率是在分配中,而不是在复制中(我怀疑与优化相比更多)。

要明确的是,这就是关于make_shared

的提升

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

  

除了方便和风格,这样的功能也是例外安全的   并且相当快,因为​​它可以使用单个分配   对象及其对应的控制块,消除了一个   shared_ptr的构造开销的重要部分。这个   消除了关于shared_ptr的一个主要效率投诉。

答案 3 :(得分:3)

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

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更有效率,因为它只能使用一个动态分配而不是两个来实现,因为它需要一个指针的内存值,而不是每个共享对象的簿记。

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