weak_ptr 如何防止循环引用?

问题描述 投票:0回答:5

我正在阅读 Scott Meyers 的《Effective C++》一书。有人提到,

tr1::shared_ptr
tr1::weak_ptr
就像内置指针一样,但它们跟踪有多少个
tr1::shared_ptrs
指向一个对象。

这称为引用计数。这可以很好地防止非循环数据结构中的资源泄漏,但如果两个或多个对象包含

tr1::shared_ptrs
从而形成循环,则即使指向该循环的所有外部指针都已被删除,该循环也可能使彼此的引用计数保持在零以上。被毁了。

这就是

tr1::weak_ptrs
发挥作用的地方。

我的问题是循环数据结构如何使引用计数大于零。我恳请一个 C++ 程序示例。

weak_ptrs
问题是如何解决的? (再次请举例)。

c++ c++11 shared-ptr weak-ptr cyclic-reference
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) .


66
投票

A

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

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

其工作方式是,只要想要使用底层对象,就可以使用

weak_ptr
创建一个
shared_ptr
。然而,如果该对象已被删除,则返回
shared_ptr
的空实例。由于底层对象的引用计数不会因
weak_ptr
引用而增加,因此循环引用不会导致底层对象不被删除。


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;  

9
投票

弱指针只是“观察”被管理对象;它们不会“使其保持活力”或影响其寿命。与

shared_ptr
不同,当最后一个
weak_ptr
超出范围或消失时,所指向的对象仍然可以存在,因为
weak_ptr
不会影响对象的生命周期 - 它没有所有权。
weak_ptr
可用于确定对象是否存在,并提供可用于引用该对象的
shared_ptr

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


-9
投票

以上所有答案都是错误的。

weak_ptr
不用于破坏循环引用,它们还有另一个目的。

基本上,如果所有

shared_ptr(s)
都是由
make_shared()
allocate_shared()
调用创建的,那么如果您除了内存之外没有其他资源需要管理,那么您将永远不需要
weak_ptr
。这些函数用对象本身创建
shared_ptr
引用计数器对象,同时内存将被释放。

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

© www.soinside.com 2019 - 2024. All rights reserved.