C++ make_shared 真的比 new 更有效率吗?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/9302296/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 12:37:36  来源:igfitidea点击:

Is make_shared really more efficient than new?

c++shared-ptrclanglibc++make-shared

提问by user1212354

I was experimenting with shared_ptrand make_sharedfrom C++11 and programmed a little toy example to see what is actually happening when calling make_shared. As infrastructure I was using llvm/clang 3.0 along with the llvm std c++ library within XCode4.

我正在尝试使用C++11shared_ptrmake_shared从 C++11 中编写一个小玩具示例,以查看调用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;
}

Now have a look at the output, please:

现在看看输出,请:

Create smart_ptr using make_shared...

Constructor make_shared

Copy constructor...

Copy constructor...

Destructor

Destructor

Create smart_ptr using make_shared: done.

Create smart_ptr using new...

Constructor new

Create smart_ptr using new: done.

Destructor

Destructor

使用 make_shared 创建 smart_ptr...

构造函数 make_shared

复制构造函数...

复制构造函数...

析构函数

析构函数

使用 make_shared:done 创建 smart_ptr。

使用新的创建 smart_ptr...

构造函数 new

使用 new: done 创建 smart_ptr。

析构函数

析构函数

It appears that make_sharedis calling the copy constructor two times. If I allocate memory for an Objectusing a regular newthis does not happen, only one Objectis constructed.

似乎make_shared是两次调用复制构造函数。如果我为Objectusing分配内存,则new不会发生这种情况,只会Object构造一个。

What I am wondering about is the following. I heard that make_sharedis supposed to be more efficient than using new(1, 2). One reason is because make_sharedallocates the reference count together with the object to be managed in the same block of memory. OK, I got the point. This is of course more efficient than two separate allocation operations.

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

On the contrary I don't understand why this has to come with the cost of two calls to the copy constructor of Object. Because of this I am not convinced that make_sharedis more efficient than allocation using newin everycase. Am I wrong here? Well OK, One could implement a move constructor for Objectbut still I am not sure whether this this is more efficient than just allocating Objectthrough new. At least not in every case. It would be true if copying Objectis less expensive than allocating memory for a reference counter. But the shared_ptr-internal reference counter could be implemented using a couple of primitive data types, right?

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

Can you help and explain why make_sharedis the way to go in terms of efficiency, despite the outlined copy overhead?

make_shared尽管概述了复制开销,您能否帮助并解释为什么要提高效率?

采纳答案by Nicol Bolas

As infrastructure I was using llvm/clang 3.0 along with the llvm std c++ library within XCode4.

作为基础设施,我在 XCode4 中使用 llvm/clang 3.0 和 llvm std c++ 库。

Well that appears to be your problem. The C++11 standard states the following requirements for make_shared<T>(and allocate_shared<T>), in section 20.7.2.2.6:

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

Requires: The expression ::new (pv) T(std::forward(args)...), where pv has type void* and points to storage suitable to hold an object of type T, shall be well formed. A shall be an allocator (17.6.3.5). The copy constructor and destructor of A shall not throw exceptions.

要求:表达式 ::new (pv) T(std::forward(args)...),其中 pv 的类型为 void* 并指向适合保存类型 T 的对象的存储,应格式良好。A 应为分配器 (17.6.3.5)。A 的复制构造函数和析构函数不应抛出异常。

Tis notrequired to be copy-constructable. Indeed, Tisn't even required to be non-placement-new constructable. It is only required to be constructable in-place. This means that the only thing that make_shared<T>can do with Tis newit in-place.

T不是需要被复制施工的。事实上,T甚至不需要是非放置新构造。它只需要可就地构建。这意味着唯一make_shared<T>可以做的T就是new就地。

So the results you get are not consistent with the standard. LLVM's libc++ is broken in this regard. File a bug report.

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

For reference, here's what happened when I took your code into VC2010:

作为参考,以下是我将您的代码放入 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

I also ported it to Boost's original shared_ptrand make_shared, and I got the same thing as VC2010.

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

I'd suggest filing a bug report, as libc++'s behavior is broken.

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

回答by Kerrek SB

You have to compare these two versions:

您必须比较这两个版本:

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

In your code, the second variable is just a naked pointer, not a shared pointer at all.

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



Now on the meat. make_sharedis(in practice) more efficient, because it allocates the reference control block together with the actual object in one single dynamic allocation. By contrast, the constructor for shared_ptrthat takes a naked object pointer must allocate anotherdynamic variable for the reference count. The trade-off is that make_shared(or its cousin allocate_shared) does not allow you to specify a custom deleter, since the allocation is performed by the allocator.

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

(This does not affect the construction of the object itself. From Object's perspective there is no difference between the two versions. What's more efficient is the shared pointer itself, not the managed object.)

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

回答by Evan Teran

So one thing to keep in mind is your optimization settings. Measuring performance, particularly with regard to c++ is meaninglesswithout optimizations enabled. I don't know if you did in fact compile with optimizations, so I thought it was worth mentioning.

所以要记住的一件事是您的优化设置。如果没有启用优化,测量性能,尤其是关于 c++ 的性能是没有意义的。我不知道您是否确实进行了优化编译,所以我认为值得一提。

That said, what you are measuring with this test is nota way that make_sharedis more efficient. Simply put, you are measuring the wrong thing :-P.

也就是说,你用这个测试测量的不是一种make_shared更有效的方法。简而言之,您测量的是错误的东西:-P。

Here's the deal. Normally, when you create shared pointer, it has at least 2 data members (possibly more). One for the pointer, and one for the reference count. This reference count is allocated on the heap (so that it can be shared among shared_ptrwith different lifetimes...that's the point after all!)

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

So if you are creating an object with something like std::shared_ptr<Object> p2(new Object("foo"));There are at least 2calls to new. One for Objectand one for the reference count object.

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

make_sharedhas the option (i'm not sure it has to), to do a single newwhich is big enough to hold the object pointed to and the reference count in the same contiguous block. Effectively allocating an object that looks something like this (illustrative, not literally what it is).

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

struct T {
    int reference_count;
    Object object;
};

Since the reference count and the object's lifetimes are tied together (it doesn't make sense for one to live longer than the other). This whole block can be deleted at the same time as well.

因为引用计数和对象的生命周期是联系在一起的(一个人比另一个人活得更久是没有意义的)。整个块也可以同时被deleted。

So the efficiency is in allocations, not in copying (which I suspect had to do with optimization more than anything else).

所以效率在于分配,而不是复制(我怀疑这与优化有关,而不是其他任何事情)。

To be clear, this is what boost has to say on about make_shared

需要明确的是,这就是 boost 不得不说的 make_shared

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

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

Besides convenience and style, such a function is also exception safe and considerably faster because it can use a single allocation for both the object and its corresponding control block, eliminating a significant portion of shared_ptr's construction overhead. This eliminates one of the major efficiency complaints about shared_ptr.

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

回答by bames53

You should not be getting any extra copies there. The output should be:

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

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

I don't know why you're getting extra copies. (though I see you're getting one 'Destructor' too many, so the code you used to get your output must be different from the code you posted)

我不知道为什么你会得到额外的副本。(虽然我看到你得到了太多的“析构函数”,所以你用来获取输出的代码必须与你发布的代码不同)

make_sharedis more efficient because it can be implemented using only one dynamic allocation instead of two, and because it needs one pointer's worth of memory less book-keeping per shared object.

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

Edit: I didn't check with Xcode 4.2 but with Xcode 4.3 I get the correct output I show above, not the incorrect output shown in the question.

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