web-dev-qa-db-ja.com

shared_ptrとweak_ptrの違い

私はスコット・マイヤーズの「効果的なC++」の本を読んでいます。 tr1::shared_ptrtr1::weak_ptrが組み込みポインターのように動作することが言及されましたが、それらはオブジェクトを指すtr1::shared_ptrsの数を追跡します。

これは参照カウントと呼ばれます。これは、非循環データ構造でのリソースリークの防止に役立ちますが、2つ以上のオブジェクトにtr1::shared_ptrsが含まれており、サイクルが形成されると、サイクルへのすべての外部ポインターが、破壊されました。

それがtr1::weak_ptrsの出番です。

私の質問は、循環データ構造がどのように参照カウントをゼロ以上にするかです。サンプルC++プログラムを親切にリクエストします。 weak_ptrsによって問題はどのように解決されますか? (もう一度、例を挙げてください)。

69
venkysmarty

shared_ptrは、生のポインターの周りに参照カウント機構をラップします。したがって、shared_ptrの各インスタンスについて、参照カウントは1ずつ増加します。 2つのshare_ptrオブジェクトがお互いを参照する場合、参照カウントがゼロになることはないため、削除されません。

weak_ptrshared_ptrを指しますが、その参照カウントは増加しません。これは、weak_ptr参照が存在する場合でも、基になるオブジェクトを削除できることを意味します。

これが機能する方法は、weak_ptrを使用して、基になるオブジェクトを使用するときにshared_ptrを作成できることです。ただし、オブジェクトが既に削除されている場合は、shared_ptrの空のインスタンスが返されます。基礎となるオブジェクトの参照カウントはweak_ptr参照で増加しないため、循環参照では基礎となるオブジェクトは削除されません。

47
doron

私の質問を繰り返しましょう:「私の質問、どのように循環データ構造が参照カウントをゼロ以上にするか、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_ptrcannot help。サイクルがある場合、プログラマはそれを見つけてそれを破らなければなりません。別の解決策は、フルガベージコレクション(Java、C#、Go、Haskellなど)を備えたプログラミング言語を使用するか、C/C++で動作する保守的な(=不完全な)ガベージコレクター(Boehm GCなど)を使用することです。 。

108
user811773

将来の読者のために。
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;  
17
newprint

弱いポインターは、管理対象オブジェクトを「観察」するだけです。それらは「生き続ける」ことも、その寿命に影響することもありません。 _shared_ptr_とは異なり、最後の_weak_ptr_が範囲外になるか消えると、_weak_ptr_はオブジェクトの存続期間に影響を与えないため、ポイント先のオブジェクトは引き続き存在できます-所有権はありません権利。 _weak_ptr_を使用して、オブジェクトが存在するかどうかを判別し、それを参照するために使用できる_shared_ptr_を提供できます。

_weak_ptr_の定義は、比較的簡単にできるように設計されているため、結果として_weak_ptr_で直接実行できることはほとんどありません。たとえば、逆参照することはできません。 _operator*_に対して_operator->_も_weak_ptr_も定義されていません。オブジェクトへのポインタにアクセスすることはできません-get()関数はありません。 _weak_ptrs_を順序付けられたコンテナに格納できるように定義された比較関数がありますが、それだけです。

4
peterDriscoll