shared_ptr 和 weak_ptr 有什么区别?

88
我正在阅读Scott Meyers的《Effective C++》一书。其中提到了tr1::shared_ptrtr1::weak_ptr像内置指针一样工作,但它们会跟踪有多少个tr1::shared_ptrs指向一个对象。
这被称为引用计数。它在预防非循环数据结构中的资源泄漏方面效果很好,但如果两个或更多的对象包含tr1::shared_ptrs,使得形成了一个循环,则即使所有指向该循环的外部指针已被销毁,循环仍可能保持彼此的引用计数不为零。
这就是tr1::weak_ptrs的作用所在。
我的问题是:循环数据结构如何使引用计数保持不为零?我请求一个C++程序示例。如何使用weak_ptrs来解决这个问题?(请再次给出示例)。

“weak_ptrs如何解决问题?”它并没有解决问题,而是通过适当的设计来避免所有权循环的存在。 - curiousguy
5个回答

135

让我重复您的问题:“我的问题是关于循环数据结构如何使引用计数大于零,请求用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)。


但是现在必须准备好对B::a的所有使用,以使弱引用失效。如果不是这种情况,则意味着weak_ptr不是一个适当的工具。 - curiousguy
3
如果B::a是一个weak_ptr,那么任何东西都不应该依赖它的存在 - 因为它不拥有a。在这里,A::b才是可依赖的对象。 - rich.e
4
我会选择你的回答作为最佳答案。但是嘿..这不是我的选择 :) 顺便点个赞+1 - Paul

66

shared_ptr将引用计数机制包装在原始指针周围。因此,每个shared_ptr实例的引用计数增加1。如果两个shared_ptr对象相互引用,则它们永远不会被删除,因为它们永远不会达到0的引用计数。

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

其工作方式是使用weak_ptr创建shared_ptr,以便在需要使用底层对象时使用。但如果对象已经被删除,则返回空的shared_ptr实例。由于weak_ptr引用不会增加底层对象的引用计数,因此循环引用将不会导致底层对象未被删除。


10
我认为该参考控制对象会计算“使用次数”(shared_ptr)和“弱引用”(weak_ptr +(Uses> 0?1:0))的总数。但这可能是一项实现细节。 - Ben L

21

给未来的读者。


只想指出,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

9
弱指针仅仅是“观察”托管对象,不会“保持它的存活”,也不会影响其生命周期。与shared_ptr不同的是,当最后一个weak_ptr出了作用域或消失时,所指向的对象仍然可能存在,因为weak_ptr不会影响对象的生命周期 - 它没有所有权。可以使用weak_ptr来确定对象是否存在,并提供用于引用该对象的shared_ptr。
weak_ptr的定义旨在使其相对易于使用,因此直接使用weak_ptr的操作非常有限。例如,您无法解引用它;weak_ptr没有定义operator*或operator->。您无法使用它访问指向对象的指针 - 没有get()函数。定义了一个比较函数,以便您可以将weak_ptr存储在有序容器中,但仅此而已。

-8

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

基本上,如果所有的shared_ptr(s)都是由make_shared()allocate_shared()调用创建的,则如果您没有除内存之外的资源需要管理,您永远不需要weak_ptr。这些函数会将shared_ptr引用计数对象与对象本身一起创建,内存将同时释放。

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


"weak_ptr没有被用来打破循环引用" +1 "_它们有其他的目的。" 但是这个答案并没有解释释放作用,所以-1。 - curiousguy
如果所有的shared_ptr都是通过make_shared()创建的,那么你就不需要使用shared_ptr了(unique_ptr更为适合)。引用计数的价值在于多个对象持有引用。这是通过赋值发生的,就像非常好的例子一样。 - jwm

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接