C ++中make_shared和normal_ shared_ptr的区别 [英] Difference in make_shared and normal shared_ptr in C++
问题描述
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
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 ++允许对子表达式求任意次序,所以一个可能的排序是:
-
new Lhs(foo))
-
new Rhs(bar))
-
std :: shared_ptr< Lhs>
-
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>对控制块和托管对象执行了两次堆分配$ 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:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
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_ptr
s keep the control block alive?
There must be a way for weak_ptr
s to determine if the managed object is still valid (eg. for lock
). They do this by checking the number of shared_ptr
s 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_ptr
s or weak_ptr
s 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_ptr
s alive, and free the memory for the control block (maybe later) when there are no weak_ptr
s alive.
这篇关于C ++中make_shared和normal_ shared_ptr的区别的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!