C++中make_shared和普通shared_ptr的区别

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/20895648/
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 23:23:07  来源:igfitidea点击:

Difference in make_shared and normal shared_ptr in C++

c++c++11shared-ptr

提问by Anup Buchke

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

Many google and stackoverflow posts are there on this, but I am not able to understand why make_sharedis more efficient than directly using shared_ptr.

许多 google 和 stackoverflow 帖子都在这上面,但我不明白为什么make_shared比直接使用shared_ptr.

Can someone explain me step by step sequence of objects created and operations done by both so that I will be able to understand how make_sharedis efficient. I have given one example above for reference.

有人可以一步一步地向我解释创建的对象和两者完成的操作的顺序,以便我能够理解make_shared效率如何。上面我举了一个例子供参考。

回答by mpark

The difference is that std::make_sharedperforms one heap-allocation, whereas calling the std::shared_ptrconstructor performs two.

区别在于std::make_shared执行一次堆分配,而调用std::shared_ptr构造函数执行两次。

Where do the heap-allocations happen?

堆分配在哪里发生?

std::shared_ptrmanages two entities:

std::shared_ptr管理两个实体:

  • the control block (stores meta data such as ref-counts, type-erased deleter, etc)
  • the object being managed
  • 控制块(存储元数据,例如引用计数、类型擦除的删除器等)
  • 被管理的对象

std::make_sharedperforms a single heap-allocation accounting for the space necessary for both the control block and the data. In the other case, new Obj("foo")invokes a heap-allocation for the managed data and the std::shared_ptrconstructor performs another one for the control block.

std::make_shared为控制块和数据所需的空间执行单个堆分配。在另一种情况下,new Obj("foo")为托管数据调用堆分配,std::shared_ptr构造函数为控制块执行另一个。

For further information, check out the implementation notesat cppreference.

欲了解更多信息,请查看执行音符cppreference

Update I: Exception-Safety

更新 I:异常安全

NOTE (2019/08/30): This is not a problem since C++17, due to the changes in the evaluation order of function arguments. Specifically, each argument to a function is required to fully execute before evaluation of other arguments.

注意 (2019/08/30):由于函数参数的计算顺序发生了变化,这不是 C++17 以来的问题。具体来说,函数的每个参数都需要在评估其他参数之前完全执行。

Since the OP seem to be wondering about the exception-safety side of things, I've updated my answer.

由于 OP 似乎对事物的异常安全方面感到疑惑,因此我更新了我的答案。

Consider this example,

考虑这个例子,

void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }

F(std::shared_ptr<Lhs>(new Lhs("foo")),
  std::shared_ptr<Rhs>(new Rhs("bar")));

Because C++ allows arbitrary order of evaluation of subexpressions, one possible ordering is:

因为 C++ 允许对子表达式进行任意的求值顺序,所以一种可能的顺序是:

  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>
  1. new Lhs("foo"))
  2. new Rhs("bar"))
  3. std::shared_ptr<Lhs>
  4. std::shared_ptr<Rhs>

Now, suppose we get an exception thrown at step 2 (e.g., out of memory exception, Rhsconstructor threw some exception). We then lose memory allocated at step 1, since nothing will have had a chance to clean it up. The core of the problem here is that the raw pointer didn't get passed to the std::shared_ptrconstructor immediately.

现在,假设我们在第 2 步中抛出了一个异常(例如,内存不足异常,Rhs构造函数抛出了一些异常)。然后我们丢失了在第 1 步分配的内存,因为没有任何东西可以清理它。这里问题的核心是原始指针没有std::shared_ptr立即传递给构造函数。

One way to fix this is to do them on separate lines so that this arbitary ordering cannot occur.

解决此问题的一种方法是在单独的行上执行它们,以便不会发生这种任意排序。

auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);

The preferred way to solve this of course is to use std::make_sharedinstead.

解决这个问题的首选方法当然是使用std::make_shared

F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));

Update II: Disadvantage of std::make_shared

更新二:缺点 std::make_shared

Quoting Casey's comments:

引用凯西的评论:

Since there there's only one allocation, the pointee's memory cannot be deallocated until the control block is no longer in use. A weak_ptrcan keep the control block alive indefinitely.

由于只有一次分配,因此在控制块不再使用之前,不能释放指针对象的内存。Aweak_ptr可以使控制块无限期地保持活动状态。

Why do instances of weak_ptrs keep the control block alive?

为什么weak_ptrs 的实例使控制块保持活动状态?

There must be a way for weak_ptrs to determine if the managed object is still valid (eg. for lock). They do this by checking the number of shared_ptrs that own the managed object, which is stored in the control block. The result is that the control blocks are alive until the shared_ptrcount and the weak_ptrcount both hit 0.

weak_ptrs必须有一种方法来确定托管对象是否仍然有效(例如 for lock)。他们通过检查shared_ptr拥有托管对象的s的数量来做到这一点,托管对象存储在控制块中。结果是控制块一直有效,直到shared_ptr计数和weak_ptr计数都达到 0。

Back to std::make_shared

回到 std::make_shared

Since std::make_sharedmakes a single heap-allocation for both the control block and the managed object, there is no way to free the memory for control block and the managed object independently. We must wait until we can free both the control block and the managed object, which happens to be until there are no shared_ptrs or weak_ptrs alive.

由于std::make_shared对控制块和托管对象进行了单个堆分配,因此无法独立地为控制块和托管对象释放内存。我们必须等到我们可以释放控制块和托管对象,这恰好是直到没有shared_ptrs 或weak_ptrs 存活。

Suppose we instead performed two heap-allocations for the control block and the managed object via newand shared_ptrconstructor. Then we free the memory for the managed object (maybe earlier) when there are no shared_ptrs alive, and free the memory for the control block (maybe later) when there are no weak_ptrs alive.

假设我们代替执行的两个堆分配的控制块,并经由被管理对象newshared_ptr构造函数。然后我们在没有shared_ptrs 存活时释放托管对象的内存(可能更早),并在没有s 存活时释放控制块的内存(可能稍后)weak_ptr

回答by Mike Seymour

The shared pointer manages both the object itself, and a small object containing the reference count and other housekeeping data. make_sharedcan allocate a single block of memory to hold both of these; constructing a shared pointer from a pointer to an already-allocated object will need to allocate a second block to store the reference count.

共享指针既管理对象本身,也管理包含引用计数和其他内务处理数据的小对象。make_shared可以分配一个内存块来保存这两者;从指向已分配对象的指针构造共享指针将需要分配第二个块来存储引用计数。

As well as this efficiency, using make_sharedmeans that you don't need to deal with newand raw pointers at all, giving better exception safety - there is no possibility of throwing an exception after allocating the object but before assigning it to the smart pointer.

除了这种效率,使用make_shared意味着您根本不需要处理new原始指针,从而提供更好的异常安全性 - 在分配对象之后但在将其分配给智能指针之前不可能抛出异常。

回答by Dr_Sam

There is another case where the two possibilities differ, on top of those already mentioned: if you need to call a non-public constructor (protected or private), make_shared might not be able to access it, while the variant with the new works fine.

还有另一种情况,这两种可能性不同,除了已经提到的那些:如果您需要调用非公共构造函数(受保护的或私有的),make_shared 可能无法访问它,而新的变体可以正常工作.

class A
{
public:

    A(): val(0){}

    std::shared_ptr<A> createNext(){ return std::make_shared<A>(val+1); }
    // Invalid because make_shared needs to call A(int) **internally**

    std::shared_ptr<A> createNext(){ return std::shared_ptr<A>(new A(val+1)); }
    // Works fine because A(int) is called explicitly

private:

    int val;

    A(int v): val(v){}
};

回答by Simon Ferquel

If you need special memory alignment on the object controlled by shared_ptr, you cannot rely on make_shared, but I think it's the only one good reason about not using it.

如果您需要对 shared_ptr 控制的对象进行特殊的内存对齐,则不能依赖 make_shared,但我认为这是不使用它的唯一一个很好的理由。

回答by icebeat

I see one problem with std::make_shared, it doesn't support private/protected constructors

我看到 std::make_shared 有一个问题,它不支持私有/受保护的构造函数

回答by James

Shared_ptr: Performs two heap allocation

Shared_ptr: 执行两次堆分配

  1. Control block(reference count)
  2. Object being managed
  1. 控制块(引用计数)
  2. 被管理的对象

Make_shared: Performs only one heap allocation

Make_shared: 只执行一次堆分配

  1. Control block and object data.
  1. 控制块和对象数据。

回答by Martin Vorbrodt

I think the exception safety part of mr mpark's answer is still a valid concern. when creating a shared_ptr like this: shared_ptr< T >(new T), the new T may succeed, while the shared_ptr's allocation of control block may fail. in this scenario, the newly allocated T will leak, since the shared_ptr has no way of knowing that it was created in-place and it is safe to delete it. or am I missing something? I don't think the stricter rules on function parameter evaluation help in any way here...

我认为 mpark 先生回答中的异常安全部分仍然是一个有效的问题。当像这样创建一个 shared_ptr 时:shared_ptr<T>(new T),new T 可能会成功,而 shared_ptr 的控制块分配可能会失败。在这种情况下,新分配的 T 将泄漏,因为 shared_ptr 无法知道它是就地创建的,删除它是安全的。或者我错过了什么?我认为更严格的函数参数评估规则在这里没有任何帮助......

回答by orlando

About efficiency and concernig time spent on allocation, I made this simple test below, I created many instances through these two ways (one at a time):

关于效率和分配时间,我做了下面这个简单的测试,我通过这两种方式(一次一个)创建了很多实例:

for (int k = 0 ; k < 30000000; ++k)
{
    // took more time than using new
    std::shared_ptr<int> foo = std::make_shared<int> (10);

    // was faster than using make_shared
    std::shared_ptr<int> foo2 = std::shared_ptr<int>(new int(10));
}

The thing is, using make_shared took the double time compared with using new. So, using new there are two heap allocations instead of one using make_shared. Maybe this is a stupid test but doesn't it show that using make_shared takes more time than using new? Of course, I'm talking about time used only.

问题是,与使用 new 相比,使用 make_shared 花费了两倍的时间。因此,使用 new 有两个堆分配,而不是使用 make_shared 的一个。也许这是一个愚蠢的测试,但它不是表明使用 make_shared 比使用 new 花费更多时间吗?当然,我说的只是使用的时间。