web-dev-qa-db-ja.com

「enable_shared_from_this」の有用性は何ですか?

Boost.Asioの例を読んでいるときにenable_shared_from_thisに出くわしましたが、ドキュメントを読んだ後、これを正しく使用する方法が失われています。誰かが私に例を教えてもらえますか、このクラスを使用するときの説明が理にかなっています。

327
fido

有効なshared_ptrインスタンスをthisに取得できるのは、thisだけである場合です。それなしでは、既にメンバーとして持っていない限り、shared_ptrthisに取得する方法はありません。 enable_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_thisはC++ 11標準の一部になりました。ブーストからだけでなく、そこからも入手できます。

339

弱いポインターに関するドブス博士の記事から、この例は理解しやすいと思います(ソース: 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
}
183

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

enable_shared_from_this<T>は、Tのインスタンスに対して '1つの真の参照カウント'を保持するプライベートweak_ptr<T>インスタンスをTに追加します。

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

Tは、そのメソッドでshared_from_thisを呼び出してshared_ptr<T>のインスタンスを取得できます。このインスタンスは内部的に保存されている同じ参照カウントに戻りますです。この方法では、お互いを知らない複数のT*インスタンスを持たず、それぞれがshared_ptrであると考えるのではなく、shared_ptrの参照カウントが保存される場所が常に1つありますref-counting Tを担当し、ref-countがゼロに達したら削除します。

27
mackenir

Boost :: intrusive_ptrを使用しても、この問題は発生しません。多くの場合、これはこの問題を回避するためのより便利な方法です。

3
blais

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

他のWordでは、このようにコードを変えることができます

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;
};           
2
mchiasson