shared_ptr和weak_ptr的区别


76

我正在阅读Scott Meyers的“ Effective C ++”书。有人提到,有tr1::shared_ptr并且tr1::weak_ptr像内置指​​针一样工作,但是它们会跟踪tr1::shared_ptrs指向一个对象的数量。

这称为参考计数。这样可以很好地防止非循环数据结构中的资源泄漏,但是,如果两个或多个对象包含tr1::shared_ptrs一个循环,则该循环可以使彼此的引用计数保持在零以上,即使指向该循环的所有外部指针都已被破坏。

那就是tr1::weak_ptrs进来的地方。

我的问题是循环数据结构如何使引用计数大于零。我请一个示例C ++程序。问题如何解决weak_ptrs?(再次,请举个例子)。


weak_ptrs如何解决问题? ”不是。通过适当的设计(不存在所有权周期)可以解决此问题。
curiousguy

Answers:


52

Ashared_ptr将引用计数机制包装在原始指针周围。因此,对于每个引用实例,shared_ptr引用计数都增加一。如果两个share_ptr对象相互引用,它们将永远不会被删除,因为它们永远不会以零的引用计数结束。

weak_ptr指向ashared_ptr但不会增加其引用计数。这意味着即使有weak_ptr引用,底层对象仍可以删除。

这种工作方式是,只要有人想使用基础对象,weak_ptr就可以使用来创建shared_ptrfor。但是,如果该对象已被删除,shared_ptr则返回a的空实例。由于基础对象上的引用计数不会随weak_ptr引用一起增加,因此循环引用不会导致基础对象不被删除。


10
我相信引用控制对象会同时保留“ Uses”(shared_ptrs)和“ Weaks”的数量(weak_ptrs +(Uses> 0?1:0))。但这可能是一个具体细节。
本L

121

让我重复您的问题:“我的问题是,循环数据结构如何使引用计数大于零,请请求在C ++程序中用示例显示。weak_ptrs请用示例再次解决问题。”

这样的C ++代码会发生此问题(从概念上):

class A { shared_ptr<B> b; ... };
class B { shared_ptr<A> a; ... };
shared_ptr<A> x(new A);  // +1
x->b = new B;            // +1
x->b->a = x;             // +1
// Ref count of 'x' is 2.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, there will be a memory leak:
// 2 is decremented to 1, and so both ref counts will be 1.
// (Memory is deallocated only when ref count drops to 0)

要回答问题的第二部分:在引用计数上处理周期在数学上是不可能的。因此,a weak_ptr(基本上只是的简化版本shared_ptr不能用于解决循环问题-程序员正在解决循环问题。

为了解决这个问题,程序员需要了解对象之间的所有权关系,或者如果自然不存在这种所有权,就需要发明一种所有权关系。

可以更改上面的C ++代码,以便A拥有B:

class A { shared_ptr<B> b; ... };
class B { weak_ptr<A>   a; ... };
shared_ptr<A> x(new A); // +1
x->b = new B;           // +1
x->b->a = x;            // No +1 here
// Ref count of 'x' is 1.
// Ref count of 'x->b' is 1.
// When 'x' leaves the scope, its ref count will drop to 0.
// While destroying it, ref count of 'x->b' will drop to 0.
// So both A and B will be deallocated.

一个关键问题是:weak_ptr如果程序员由于缺乏特权或信息而无法告知所有权关系并且无法建立任何静态所有权,是否可以使用?

答案是:如果对象之间的所有权不清楚,weak_ptr 无济于事。如果有一个循环,程序员必须找到它并中断它。一种替代方法是使用具有完整垃圾收集的编程语言(例如:Java,C#,Go,Haskell),或使用与C / C ++一起使用的保守的(=不完美)垃圾收集器(例如:Boehm GC) 。


但是,B::a现在必须对的所有使用做好准备,使弱引用失效。如果不是这种情况,则意味着weak_ptr它不是适当的工具。
curiousguy

3
如果B :: a是weak_ptr,则没有任何内容取决于它的存在-因为它不拥有a。A :: b是这里的可靠人。
rich.e 2012年

4
我会选择您的最佳答案。但是,嘿..不是我的选择:) +1 btw
保罗

18

对于未来的读者。
只想指出Atom给出的解释很好,这是工作代码

#include <memory> // and others
using namespace std;

    class B; // forward declaration 
    // for clarity, add explicit destructor to see that they are not called
    class A { public: shared_ptr<B> b; ~A() {cout << "~A()" << endl; } };  
    class B { public: shared_ptr<A> a; ~B() {cout << "~B()" << endl; } };     
    shared_ptr<A> x(new A);  //x->b share_ptr is default initialized
    x->b = make_shared<B>(); // you can't do "= new B" on shared_ptr                      
    x->b->a = x;
    cout << x.use_count() << endl;  

“ weak_ptr”仅用于循环引用还是用于缓存?
g10guang

7

弱指针只是“观察”被管理对象。他们不会“保持生命”或影响生命周期。与不同的是shared_ptr,当最后一个weak_ptr超出范围或消失时,指向的对象仍然可以存在,因为weak_ptr不会影响对象的生存期-它没有所有权。所述weak_ptr可用于确定对象是否存在,并提供一种shared_ptr可用于指它。

的定义weak_ptr旨在使其相对简单,因此您几乎无法直接使用weak_ptr。例如,您不能取消引用它;既未定义,operator*operator->未定义weak_ptr。您无法使用它访问指向对象的指针-没有get()功能。定义了一个比较函数,以便您可以将其存储weak_ptrs在有序容器中,仅此而已。


-6

以上所有答案都是错误的。weak_ptr不用于破坏循环引用,它们还有另一个用途。

基本上,如果所有资源shared_ptr(s)都是由make_shared()allocate_shared()调用创建的,那么除了没有需要weak_ptr管理的资源以外,您将永远不需要。这些函数shared_ptr使用对象本身创建参考计数器对象,并且将同时释放内存。

weak_ptr和之间的唯一区别shared_ptr是,weak_ptr允许在释放实际对象之后保留参考计数器对象。因此,如果你一直有很多shared_ptrstd::set实际的对象会占用大量的内存,如果他们足够大。可以通过weak_ptr改用解决此问题。在这种情况下,必须确保weak_ptr容器中存储的存储在使用前没有过期。


weak_ptr不用于破坏循环引用” +1“,它们还有其他用途。 ”但是该答案不能解释释放目的,因此-1
curiousguy16年

如果所有的shared_ptr是由make_shared()创建的”,那么您就不需要shared_ptrunique_ptr更合适)。引用计数的值是当多个对象保存引用时。这是通过分配完成的,例如非常好的示例。
jwm
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.