Boost.Asioの例を読んでいるときにenable_shared_from_this
に出くわしましたが、ドキュメントを読んだ後、これを正しく使用する方法が失われています。誰かが私に例を教えてもらえますか、このクラスを使用するときの説明が理にかなっています。
有効なshared_ptr
インスタンスをthis
に取得できるのは、this
だけである場合です。それなしでは、既にメンバーとして持っていない限り、shared_ptr
をthis
に取得する方法はありません。 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標準の一部になりました。ブーストからだけでなく、そこからも入手できます。
弱いポインターに関するドブス博士の記事から、この例は理解しやすいと思います(ソース: 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_pt
rオブジェクト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
}
以下に、ナットとボルトの観点からの私の説明を示します(一番上の答えは私と「クリック」しませんでした)。 *これは、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がゼロに達したら削除します。
Boost :: intrusive_ptrを使用しても、この問題は発生しません。多くの場合、これはこの問題を回避するためのより便利な方法です。
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;
};