make_shared真的比new更有效率吗

Is make_shared really more efficient than new?

本文关键字:有效率 new shared 真的 make      更新时间:2023-10-16

我用C++11中的shared_ptrmake_shared进行了实验,并编写了一个小玩具示例来查看调用make_shared时实际发生了什么。作为基础设施,我在XCode4中使用了llvm/clang 3.0以及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:done创建smart_ptr。

使用新创建smart_ptr。。。

新建

使用new:done创建smart_ptr。

自毁

自毁

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

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

相反,我不明白为什么这需要两次调用Object的复制构造函数。因此,我不相信在每个的情况下,make_shared比使用new的分配更有效。我错了吗?好吧,可以为Object实现一个移动构造函数,但我仍然不确定这是否比仅仅分配Objectnew更有效。至少不是在所有情况下。如果复制CCD_ 17比为参考计数器分配内存便宜,那将是正确的。但是shared_ptr内部引用计数器可以使用两个基元数据类型来实现,对吧?

您能否帮助并解释为什么make_shared在效率方面是可行的,尽管有概述的复制开销?

作为基础设施,我在XCode4中使用了llvm/clang 3.0以及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

我还将它移植到Boost的原始shared_ptrmake_shared,得到了与VC2010相同的东西。

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

您必须比较这两个版本:

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的角度来看,这两个版本没有区别。更有效的是共享指针本身,而不是托管对象。)

因此,需要记住的一件事是您的优化设置。在没有启用优化的情况下,衡量性能,特别是与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;
};

由于引用计数和对象的寿命是绑定在一起的(其中一个比另一个寿命长是没有意义的)。整个块也可以同时是CCD_ 41d。

因此,效率在于分配,而不是复制(我怀疑这与优化有关)。

需要明确的是,这就是boost对make_shared 的看法

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

除了方便和风格,这样的功能也是例外安全的而且速度要快得多,因为它可以对对象及其相应的控制块,消除sharedptr的构建开销的很大一部分。这消除了关于sharedptr的主要效率抱怨之一。

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

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"太多了,所以你用来获得输出的代码必须与你发布的代码不同)

make_shared更高效,因为它可以只使用一个动态分配而不是两个动态分配来实现,并且因为它需要为每个共享对象保留一个指针的内存更少的账簿。

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