`enable_shared_from_this`の有用性は何ですか?


349

enable_shared_from_thisBoost.Asioの例を読んでいる間、私は偶然見つけましたが、ドキュメントを読んだ後でも、これを正しく使用する方法に迷っています。誰かが私に例を挙げて、このクラスを使用するときの説明が理にかなっていますか?

回答:


362

持っているものがすべてあるshared_ptr場合にthis、有効なインスタンスをに取得できますthis。それがないと、すでにメンバーとして持っているのでなければ、shared_ptrto を取得する方法はありませんthisenable_shared_from_thisブーストのドキュメントからのこの例:

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_from_this();
    }
}

int main()
{
    shared_ptr<Y> p(new Y);
    shared_ptr<Y> q = p->f();
    assert(p == q);
    assert(!(p < q || q < p)); // p and q must share ownership
}

このメソッドf()は、shared_ptrメンバーインスタンスがなくても、有効なを返します。これを単純に行うことはできないことに注意してください。

class Y: public enable_shared_from_this<Y>
{
public:

    shared_ptr<Y> f()
    {
        return shared_ptr<Y>(this);
    }
}

これが返す共有ポインタは、「適切な」ものとは異なる参照カウントを持ち、オブジェクトが削除されると、そのうちの1つがぶら下がっている参照を失って保持することになります。

enable_shared_from_thisC ++ 11標準の一部となっています。ブーストだけでなく、そこからも取得できます。


202
+1。重要な点は、shared_ptr <Y>(this)を返すだけの「明白な」手法が壊れていることです。これは、個別の参照カウントを持つ複数の異なるshared_ptrオブジェクトを作成する必要があるためです。このため、同じrawポインタから複数のshared_ptrを作成してはなりません。
j_random_hacker 2009

3
C ++ 11以降では、から継承する場合生のポインタでコンストラクタを使用することは完全に有効であることに注意してください。Boostのセマンティクスがこれをサポートするように更新されたかどうかはわかりませんstd::shared_ptr std::enable_shared_from_this
マシュー

6
@MatthewHolder見積もりはありますか?cppreference.comで、「std::shared_ptr別のオブジェクトによって既に管理されているオブジェクトのを作成してもstd::shared_ptr、内部に格納されている弱い参照は参照されないため、未定義の動作が発生します。」(en.cppreference.com/w/cpp/memory/enable_shared_from_this
するThorbjörnLindeijer

5
なんでできないのshared_ptr<Y> q = p
Dan M.

2
@ThorbjørnLindeijer、あなたは正しい、それはC ++ 17以降です。一部の実装は、リリース前にC ++ 16セマンティクスに準拠していました。C ++ 11からC ++ 14の適切な処理は、を使用することstd::make_shared<T>です。
マシュー

198

ウィークポインターに関するドブス博士の記事から、この例は理解しやすいと思います(ソース:http : //drdobbs.com/cpp/184402026):

...このようなコードは正しく機能しません:

int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);

2つのshared_ptrオブジェクトはどちらも他方を認識していないため、破棄されると、両方がリソースを解放しようとします。それは通常問題を引き起こします。

同様に、メンバー関数がshared_ptr呼び出されているオブジェクトを所有するオブジェクトを必要とする場合、その場でオブジェクトを作成することはできません。

struct S
{
  shared_ptr<S> dangerous()
  {
     return shared_ptr<S>(this);   // don't do this!
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->dangerous();
   return 0;
}

このコードには、以前の例と同じ問題がありますが、形式はより微妙です。作成されると、shared_ptrオブジェクトsp1は新しく割り当てられたリソースを所有します。メンバー関数内のコードS::dangerousはそのshared_ptrオブジェクトを認識しないため、shared_ptr返されるオブジェクトはとは異なりますsp1。新しいshared_ptrオブジェクトをにコピーしsp2ても役に立ちません。ときsp2スコープ外になる、それがリソースを解放する、とするときsp1スコープ外になる、それが再びリソースを解放します。

この問題を回避する方法は、クラステンプレートを使用することenable_shared_from_thisです。テンプレートは、1つのテンプレートタイプ引数を取ります。これは、管理対象リソースを定義するクラスの名前です。そのクラスは、テンプレートからパブリックに派生する必要があります。このような:

struct S : enable_shared_from_this<S>
{
  shared_ptr<S> not_dangerous()
  {
    return shared_from_this();
  }
};

int main()
{
   shared_ptr<S> sp1(new S);
   shared_ptr<S> sp2 = sp1->not_dangerous();
   return 0;
}

これを行うときは、呼び出すオブジェクトがオブジェクトshared_from_thisによって所有されている必要があることに注意してくださいshared_ptr。これは動作しません:

int main()
{
   S *p = new S;
   shared_ptr<S> sp2 = p->not_dangerous();     // don't do this
}

15
おかげで、これは現在受け入れられている回答よりも問題がうまく解決されていることを示しています。
goertzenator 2013年

2
+1:良い答えです。余談ですが、代わりにshared_ptr<S> sp1(new S);を使用するshared_ptr<S> sp1 = make_shared<S>();ことをお勧めし
questions /

4
私はかなり確信して最後の行が読むべきだshared_ptr<S> sp2 = p->not_dangerous();ここに落とし穴は、あなたがいることであるので、あなたが呼び出す前に、通常の方法のshared_ptrを作成する必要がありますshared_from_this()最初の時間を!これは間違いやすいです。C ++ 17 より前は、1つのshared_ptrが通常の方法で作成される前に呼び出すのはUBですshared_from_this()auto sptr = std::make_shared<S>();またはshared_ptr<S> sptr(new S());。ありがたいことにC ++ 17以降では、そうするとスローされます。
AnorZaken 2016


2
@AnorZaken良い点。その修正を行うために編集リクエストを送信した場合、それは役に立ちました。私はちょうどそうしました。投稿者が主観的で状況依存のメソッド名を選択しないようにすることも、もう1つの便利な方法です。
underscore_d

30

ここに私の説明があります。一概に言えません(上の答えは私と一緒に「クリック」しませんでした)。*これは、Visual Studio 2012に付属するshared_ptrおよびenable_shared_from_thisのソースを調査した結果であることに注意してください。おそらく他のコンパイラーは、enable_shared_from_thisを異なる方法で実装しています... *

enable_shared_from_this<T>weak_ptr<T>インスタンスTの「1つの真の参照カウント」を保持するプライベートインスタンスを追加しますT

したがって、最初にshared_ptr<T>新しいT *上にを作成すると、そのT *の内部weak_ptrはrefcountが1で初期化されます。新しいものはshared_ptr基本的にthisに戻りますweak_ptr

T次いで、その方法では、呼び出すことができるshared_from_thisのインスタンスを取得するためにshared_ptr<T>、その同一の内部に記憶された参照カウントに背中を。このように、あなたは常に1位持っているT*の参照カウントが複数持つのではなく、保存されているshared_ptrお互いを知らないインスタンスを、それぞれが、彼らがいると思うshared_ptr参照カウントを担当しているTとするとき、その参照を削除します-countがゼロに達した。


1
これは正しいです。本当に重要な部分はSo, when you first create...、それが要件である(つまり、weak_ptrは、オブジェクトポインターをshared_ptr ctorに渡すまで初期化されない!)ためであり、この要件は、注意しないでください。呼び出す前にshared_ptrを作成しないshared_from_this場合はUBが取得されます-同様に、複数のshared_ptrを作成する場合もUBが取得されます。あなたは何とかあなたがshared_ptrのを作成するために必要があり、正確に一度。
AnorZaken 2016

2
つまり、からenable_shared_from_thisを取得できるようにすることがポイントなので、の全体的なアイデアは最初は脆弱ですが、実際には、ポインタを取得したときに、すでに共有されているかどうかを推測することは一般的に安全ではありません。間違った推測をするのはUBです。shared_ptr<T>T*T* t
AnorZaken 2016

" 内部weak_ptrはrefcount 1で初期化されます "弱いptrからTは所有していないスマートptrからTです。弱いptrは、他の所有するptrの「コピー」である所有するptrを作成するのに十分な情報への所有するスマートな参照です。弱いptrには参照カウントはありません。所有するすべての参照と同様に、参照カウントにアクセスできます。
curiousguy

3

boost :: intrusive_ptrを使用しても、この問題は発生しないことに注意してください。これは多くの場合、この問題を回避するためのより便利な方法です。


はい。ただしenable_shared_from_this、を特別に受け入れるAPIを使用できますshared_ptr<>。私の意見では、そのようなAPIは通常、Doing It Wrong(スタック内のより高いものにメモリを所有させる方が良いため)ですが、そのようなAPIでの作業を余儀なくされる場合、これは良いオプションです。
cdunn2001

2
できる限り標準内に留まるようにしてください。
セルゲイ

3

これはc ++ 11以降でもまったく同じです。生のポインタを提供するthisため、共有ポインタとして戻る機能を有効にしthisます。

つまり、このようにコードを回すことができます

class Node {
public:
    Node* getParent const() {
        if (m_parent) {
            return m_parent;
        } else {
            return this;
        }
    }

private:

    Node * m_parent = nullptr;
};           

これに:

class Node : std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getParent const() {
        std::shared_ptr<Node> parent = m_parent.lock();
        if (parent) {
            return parent;
        } else {
            return shared_from_this();
        }
    }

private:

    std::weak_ptr<Node> m_parent;
};           

これは、これらのオブジェクトが常にによって管理されている場合にのみ機能しますshared_ptr。あなたはそれがそうであることを確認するためにインターフェースを変更したいかもしれません。
curiousguy

1
あなたは絶対に正しい@curiousguyです。これは言うまでもありません。また、公開APIを定義するときの読みやすさを向上させるために、すべてのshared_ptrをtypedefすることも好きです。たとえば、ではなく、std::shared_ptr<Node> getParent const()通常はNodePtr getParent const()代わりに公開します。内部rawポインターへのアクセスが絶対に必要な場合(最良の例:Cライブラリーの処理)、それstd::shared_ptr<T>::getについて説明します。これは、このrawポインターアクセサーが間違った理由で何度も使用されたためです。
mchiasson 2017

-3

別の方法は、weak_ptr<Y> m_stubメンバーをに追加することclass Yです。次に書いてください:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

派生元のクラスを変更できない場合に役立ちます(他の人のライブラリを拡張するなど)。たとえばによってメンバーを初期化することを忘れないでくださいm_stub = shared_ptr<Y>(this)。それはコンストラクタの間でも有効です。

継承階層にこのようなスタブがさらに存在しても問題ありませんが、オブジェクトの破壊を防ぐことはできません。

編集:ユーザーnobarによって正しく指摘されているように、割り当てが完了して一時変数が破棄されると、コードはYオブジェクトを破棄します。したがって、私の答えは正しくありません。


4
ここであなたの意図がshared_ptr<>その指示先を削除しないaを生成することである場合、これはやりすぎです。単項関数オブジェクトreturn shared_ptr<Y>(this, no_op_deleter);がどこにno_op_deleterあり、Y*何もしないところは簡単に言えます。
John Zwinck、2011年

2
これが有効な解決策であるとは思われません。 m_stub = shared_ptr<Y>(this)これから一時的なshared_ptrを構築し、すぐに破棄します。このステートメントが終了すると、thisは削除され、後続のすべての参照がぶら下がります。
nobar

2
著者はこの答えが間違っていることを認めているので、おそらく彼はそれを単に削除することができます。しかし、彼は最後に4.5年でログインしたので、そうする可能性はありません。より強力な誰かがこの赤いニシンを削除できますか?
Tom Goodfellow

の実装を見るとenable_shared_from_this、それweak_ptr自体の(ctorによって生成された)が保持されておりshared_ptr、を呼び出したときにとして返されますshared_from_this。言い換えれば、あなたはenable_shared_from_thisすでに提供しているものを複製しています。
mchiasson 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.