我正在阅读Scott Meyers的“Effective C ++”一书。有人提到有tr1::shared_ptr
和tr1::weak_ptr
就像内置指针一样,但它们会跟踪指向对象的tr1::shared_ptrs
个数。
这称为引用计数。这在防止非循环数据结构中的资源泄漏方面效果很好,但如果两个或多个对象包含tr1::shared_ptrs
以便形成一个循环,则循环可以使彼此的引用计数保持在零以上,即使所有外部指针都指向循环已被摧毁。
这就是tr1::weak_ptrs
进来的地方。
我的问题是循环数据结构如何使引用计数高于零。我请求一个示例C ++程序。问题是如何通过weak_ptrs
解决的? (再次,请举例)。
答案 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_ptr
和shared_ptr
之间的唯一区别是weak_ptr
允许在释放实际对象后保留引用计数器对象。因此,如果在shared_ptr
中保留了大量std::set
,如果实际对象足够大,它们将占用大量内存。可以使用weak_ptr
来解决此问题。在这种情况下,您必须确保容器中存储的weak_ptr
在使用之前未过期。