shared_ptr和weak_ptr差异

时间:2011-02-13 13:26:29

标签: c++ c++11 shared-ptr weak-ptr cyclic-reference

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

这称为引用计数。这在防止非循环数据结构中的资源泄漏方面效果很好,但如果两个或多个对象包含tr1::shared_ptrs以便形成一个循环,则循环可以使彼此的引用计数保持在零以上,即使所有外部指针都指向循环已被摧毁。

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

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

5 个答案:

答案 0 :(得分:106)

让我重复一下你的问题:“我的问题是,循环数据结构如何使引用计数高于零,请在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)

回答问题的第二部分:引用计数在数学上不可能处理周期。因此,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)

答案 1 :(得分:45)

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

weak_ptr指向shared_ptr,但不会增加其引用计数。这意味着即使存在weak_ptr引用,仍然可以删除欠载对象。

这种方法的工作方式是weak_ptr可以用来为每当想要使用底层对象时创建shared_ptr。但是,如果该对象已被删除,则返回shared_ptr的空实例。由于基础对象的引用计数不会随weak_ptr引用而增加,因此循环引用不会导致基础对象不被删除。

答案 2 :(得分:17)

对于未来的读者 只是想指出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;  

答案 3 :(得分:4)

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

weak_ptr的定义旨在使其相对万无一失,因此您无法直接使用weak_ptr。例如,你不能取消引用它; operator*operator->都未定义 对于weak_ptr。您无法使用它访问指向该对象的指针 - 没有get()函数。定义了一个比较函数,以便您可以将weak_ptrs存储在已订购的容器中,但这就是全部。

答案 4 :(得分:-6)

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

基本上,如果所有shared_ptr(s)都是由make_shared()allocate_shared()来电创建的,那么如果您没有要管理的内存以外的资源,则绝不需要weak_ptr。这些函数用对象本身创建shared_ptr引用计数器对象,并且内存将同时被释放。

weak_ptrshared_ptr之间的唯一区别是weak_ptr允许在释放实际对象后保留引用计数器对象。因此,如果在shared_ptr中保留了大量std::set,如果实际对象足够大,它们将占用大量内存。可以使用weak_ptr来解决此问题。在这种情况下,您必须确保容器中存储的weak_ptr在使用之前未过期。