私はスコット・マイヤーズの「効果的なC++」の本を読んでいます。 tr1::shared_ptr
とtr1::weak_ptr
が組み込みポインターのように動作することが言及されましたが、それらはオブジェクトを指すtr1::shared_ptrs
の数を追跡します。
これは参照カウントと呼ばれます。これは、非循環データ構造でのリソースリークの防止に役立ちますが、2つ以上のオブジェクトにtr1::shared_ptrs
が含まれており、サイクルが形成されると、サイクルへのすべての外部ポインターが、破壊されました。
それがtr1::weak_ptrs
の出番です。
私の質問は、循環データ構造がどのように参照カウントをゼロ以上にするかです。サンプルC++プログラムを親切にリクエストします。 weak_ptrs
によって問題はどのように解決されますか? (もう一度、例を挙げてください)。
shared_ptr
は、生のポインターの周りに参照カウント機構をラップします。したがって、shared_ptr
の各インスタンスについて、参照カウントは1ずつ増加します。 2つのshare_ptr
オブジェクトがお互いを参照する場合、参照カウントがゼロになることはないため、削除されません。
weak_ptr
はshared_ptr
を指しますが、その参照カウントは増加しません。これは、weak_ptr
参照が存在する場合でも、基になるオブジェクトを削除できることを意味します。
これが機能する方法は、weak_ptr
を使用して、基になるオブジェクトを使用するときにshared_ptr
を作成できることです。ただし、オブジェクトが既に削除されている場合は、shared_ptr
の空のインスタンスが返されます。基礎となるオブジェクトの参照カウントはweak_ptr
参照で増加しないため、循環参照では基礎となるオブジェクトは削除されません。
私の質問を繰り返しましょう:「私の質問、どのように循環データ構造が参照カウントをゼロ以上にするか、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)
質問の2番目の部分に答えるには、参照カウントがサイクルを処理することは数学的に不可能です。したがって、weak_ptr
(基本的にshared_ptr
の単純なバージョンです)cannotを使用してサイクルの問題を解決します-プログラマーはサイクルの問題を解決しています。
それを解決するには、プログラマはオブジェクト間のownership関係を認識する必要があります。または、そのような所有権が自然に存在しない場合は所有権関係を考案する必要があります。
上記の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
cannot help。サイクルがある場合、プログラマはそれを見つけてそれを破らなければなりません。別の解決策は、フルガベージコレクション(Java、C#、Go、Haskellなど)を備えたプログラミング言語を使用するか、C/C++で動作する保守的な(=不完全な)ガベージコレクター(Boehm GCなど)を使用することです。 。
将来の読者のために。
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;
弱いポインターは、管理対象オブジェクトを「観察」するだけです。それらは「生き続ける」ことも、その寿命に影響することもありません。 _shared_ptr
_とは異なり、最後の_weak_ptr
_が範囲外になるか消えると、_weak_ptr
_はオブジェクトの存続期間に影響を与えないため、ポイント先のオブジェクトは引き続き存在できます-所有権はありません権利。 _weak_ptr
_を使用して、オブジェクトが存在するかどうかを判別し、それを参照するために使用できる_shared_ptr
_を提供できます。
_weak_ptr
_の定義は、比較的簡単にできるように設計されているため、結果として_weak_ptr
_で直接実行できることはほとんどありません。たとえば、逆参照することはできません。 _operator*
_に対して_operator->
_も_weak_ptr
_も定義されていません。オブジェクトへのポインタにアクセスすることはできません-get()
関数はありません。 _weak_ptrs
_を順序付けられたコンテナに格納できるように定義された比較関数がありますが、それだけです。