C ++中make_shared和normal_ shared_ptr的区别 [英] Difference in make_shared and normal shared_ptr in C++

查看:159
本文介绍了C ++中make_shared和normal_ shared_ptr的区别的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

很多google和stackoverflow帖子都在这里,但我不能理解为什么 make_shared 比直接使用 shared_ptr 更有效。



有人可以逐步解释我创建的对象序列和操作,这样我就能够理解 make_shared 是有效的。

不同的是 std :: make_shared

code>执行一个堆分配,而调用 std :: shared_ptr 构造函数执行两个。





std :: shared_ptr 管理两个实体:




  • 控制块(存储元数据,如引用计数,删除类型删除器等)

  • 管理



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



有关详细信息,请参阅 cppreference



更新I:异常安全



由于OP似乎想知道异常安全方面的事情,我更新了我的答案。



  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)));

因为C ++允许对子表达式求任意次序,所以一个可能的排序是:


  1. new Lhs(foo))

  2. new Rhs(bar))

  3. std :: shared_ptr< Lhs>

  4. std :: shared_ptr< Rhs>


$ b b

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



解决这个问题的一种方法是在不同的行上进行,这样就不会发生这种排序。

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

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

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



更新II:的缺点std :: make_shared



引用 Casey 的评论:


由于只有一个分配,所以在控制块不再使用之前,委托的内存不能被释放。 weak_ptr 可以使控制块永久保活。




weak_ptr 保持控制块有效?



必须有一种方法 weak_ptr s来确定被管理对象是否仍然有效(例如 lock )。它们通过检查拥有被存储在控制块中的被管对象的 shared_ptr 的数量来实现。结果是控制块是活着的,直到 shared_ptr 计数和 weak_ptr 计数都为0。



返回 std :: make_shared



$ c> std :: make_shared 为控制块和托管对象分配一个堆分配,没有办法独立释放控制块和托管对象的内存。我们必须等到我们释放控制块和管理对象,直到没有 shared_ptr s或 weak_ptr new 对控制块和托管对象执行了两次堆分配$ c>和 shared_ptr 构造函数。然后,当没有 shared_ptr 存活时,释放托管对象的内存(可能更早),并在没有控制块释放内存时 weak_ptr 活着。


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_shared is more efficient than directly using 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_shared is efficient. I have given one example above for reference.

解决方案

The difference is that std::make_shared performs one heap-allocation, whereas calling the std::shared_ptr constructor performs two.

Where do the heap-allocations happen?

std::shared_ptr manages two entities:

  • the control block (stores meta data such as ref-counts, type-erased deleter, etc)
  • the object being managed

std::make_shared performs 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_ptr constructor performs another one for the control block.

For further information, check out the implementation notes at cppreference.

Update I: Exception-Safety

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

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:

  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, Rhs constructor 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_ptr constructor immediately.

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_shared instead.

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

Update II: Disadvantage of 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_ptr can keep the control block alive indefinitely.

Why do instances of weak_ptrs keep the control block alive?

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_ptr count and the weak_ptr count both hit 0.

Back to std::make_shared

Since std::make_shared makes 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.

Suppose we instead performed two heap-allocations for the control block and the managed object via new and shared_ptr constructor. 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.

这篇关于C ++中make_shared和normal_ shared_ptr的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆