boost::shared_ptr を使用する場合の潜在的な危険は何ですか?
-
22-08-2019 - |
質問
使用中に自分の足を撃つ可能性のある方法は何ですか? boost::shared_ptr
?言い換えれば、使用するときに避けなければならない落とし穴は何ですか? boost::shared_ptr
?
解決
循環参照:元のオブジェクトへのshared_ptr<>
を持っている何かにshared_ptr<>
。もちろん、この悪循環を断ち切るためにweak_ptr<>
を使用することができます。
私は、私がコメントでおよそ話しているものの例として、以下を追加します。
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
if (parent_) parent_->frob();
}
private :
void do_frob();
shared_ptr<node> parent_;
vector< shared_ptr<node> > children_;
};
この例では、その親へのポインタを保持しているそれぞれのノードのツリーを、持っています。 FROB()メンバ関数は、何らかの理由のために、木を通って上方波紋。 (これは、完全に突飛ではなく、いくつかのGUIフレームワークは、このように動作)。
問題は、最上位のノードへの参照を紛失した場合、その最上位のノードがまだその子への強い参照を保持し、そのすべての子も両親への強い参照を保持、ということです。これは実際にコードから、このメモリがリークを木に到達する方法はありませんしながら、自分自身をクリーンアップからのすべてのインスタンスを維持する循環参照があることを意味します。
class node : public enable_shared_from_this<node> {
public :
void set_parent(shared_ptr<node> parent) { parent_ = parent; }
void add_child(shared_ptr<node> child) {
children_.push_back(child);
child->set_parent(shared_from_this());
}
void frob() {
do_frob();
shared_ptr<node> parent = parent_.lock(); // Note: parent_.lock()
if (parent) parent->frob();
}
private :
void do_frob();
weak_ptr<node> parent_; // Note: now a weak_ptr<>
vector< shared_ptr<node> > children_;
};
ここでは、親ノードが弱いポインタに置き換えられました。それはもはや、それが参照するノードの寿命の発言権を持っていません。最上位のノードは、前の例のようにスコープの外に出た場合、それはその子への強い参照を保持しつつ、それから、その子は親への強い参照を保持しません。したがって、そこにオブジェクトへの強い参照がありません、それは自分自身をクリーンアップします。ターンでは、これは、子どもたちがようにそれらをクリーンアップする原因となり、その1つの強い参照を、失うことになります。要するに、これは文句を言わない漏れます。そして、ちょうど戦略のweak_ptrとのshared_ptr <>を置き換えることにより、<>。
注:上記適用均等にはstd :: shared_ptrの<>とstd :: weak_ptrを<>それは:: shared_ptrの<>後押しし、後押しする場合と同様:: weak_ptrを<>
他のヒント
同じオブジェクトを複数の無関係shared_ptr
年代を作成します:
#include <stdio.h>
#include "boost/shared_ptr.hpp"
class foo
{
public:
foo() { printf( "foo()\n"); }
~foo() { printf( "~foo()\n"); }
};
typedef boost::shared_ptr<foo> pFoo_t;
void doSomething( pFoo_t p)
{
printf( "doing something...\n");
}
void doSomethingElse( pFoo_t p)
{
printf( "doing something else...\n");
}
int main() {
foo* pFoo = new foo;
doSomething( pFoo_t( pFoo));
doSomethingElse( pFoo_t( pFoo));
return 0;
}
関数呼び出しの引数内で、たとえば、匿名の一時的な共有ポインタを構築ます:
f(shared_ptr<Foo>(new Foo()), g());
new Foo()
を実行するために、その後、これまでに設定されているg()
ずに、例外をスローするために呼び出さg()
、およびshared_ptr
許されるので、shared_ptr
はFoo
オブジェクトをクリーンアップする機会を持っていないためです。
同じオブジェクトへの2つのポインタを作るように注意してください。
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d( b.get() );
} // d goes out of scope here, deletes pointer
b->doSomething(); // crashes
の代わりにこれを使用する
boost::shared_ptr<Base> b( new Derived() );
{
boost::shared_ptr<Derived> d =
boost::dynamic_pointer_cast<Derived,Base>( b );
} // d goes out of scope here, refcount--
b->doSomething(); // no crash
また、shared_ptrsを保持するクラスは、コピーコンストラクタと代入演算子を定義する必要があります。
コンストラクタでshared_from_this()を使用しようとしないでください - それは動作しません。代わりにクラスを作成するための静的メソッドを作成し、それはshared_ptrのを返しています。
私は問題なくshared_ptrsへの参照を渡されました。それが保存されます前に、念のそれはコピーのmake(すなわち、クラスメンバーとしてノー参照)。
避けるべきことが 2 つあります。
電話をかける
get()
関数を使用して生のポインターを取得し、ポイント先のオブジェクトがスコープ外になった後にそれを使用します。への参照または生のポインタを渡す
shared_ptr
オブジェクトを存続させるのに役立つ内部カウントをインクリメントしないため、これも危険です。
私たちは、数週間の奇妙な行動をデバッグします。
を理由でした:の
我々はいくつかのスレッドの労働者の代わりに、「shared_from_this」に「この」に渡されます。
footgun、確かにあなたはそれをC ++ 0xの方法を実行する方法の周りにあなたの頭をラップするまで、フラストレーションの源ではない正確には:述語のほとんどは、あなたが知っていると<functional>
からの愛がshared_ptr
とうまく再生されません。幸い、std::tr1::mem_fn
は、オブジェクト、ポインタとshared_ptr
s、交換std::mem_fun
で動作しますが、あなたがstd::negate
、std::not1
、std::plus
またはshared_ptr
とそれらの古い友人のいずれかを使用したい場合は、同様std::tr1::bind
、おそらく引数プレースホルダのある居心地の良い取得するために準備すること。実際に今、あなたは基本的にすべての関数オブジェクトアダプタのbind
を使用して終了するので、これは、実際には多くの一般的なですが、あなたはすでにSTLの便利な機能に精通している場合、それはいくつかの慣れがかかるん。
(shared_ptr
のchar
のような)本当に小さなオブジェクトのためのshort
を使用すると、オーバーヘッドかもしれないが、彼らは本当に「共有」されていません。 boost::shared_ptr
は、それはブースト1.42とG ++ 4.4.3とVS2008で作成し、すべての新しい参照のための16のバイトカウントを割り当てます。 std::tr1::shared_ptr
は20のバイトを割り当てます。今、あなたはあなたのメモリーの20万のバイトがちょうど1 =カウント保持になくなっていることを意味百万明確なshared_ptr<char>
を持っている場合。間接費とメモリの断片化は言うまでもありません。お好きなプラットフォーム上で次のようにしてみます。
void * operator new (size_t size) {
std::cout << "size = " << size << std::endl;
void *ptr = malloc(size);
if(!ptr) throw std::bad_alloc();
return ptr;
}
void operator delete (void *p) {
free(p);
}
クラス定義内でこれにshared_ptrの
ここの次のポストのを参照してください。
あなたはマルチスレッドコードでshared_ptr
を使用するときに注意する必要があります。これは、同じメモリを指してshared_ptr
sのカップルは、別のスレッドによって使用されている場合になるために、その後は比較的簡単です。
shared_ptrの人気の広範な使用は、ほぼ必然的に不要と目に見えないメモリ占有が発生します。
循環参照はよく知られている原因であり、それらのいくつかは、特に、複数のプログラマが作業されて複雑なコードで見つけることが間接的で困難な場合があります。プログラマは1つの目的は、クイックフィックスとして別の参照を必要とし、彼はサイクルを閉じているかどうかを確認するために、すべてのコードを検討する時間がありませんよりも、決めることができます。この危険性は非常に過小評価されます。
あまりよく理解は、未発表の参照の問題です。オブジェクトは、多くのshared_ptrsに出て共有されている場合はそれらの一つ一つがゼロまたは範囲の外に出るれるまで、それが破壊されることはありません。これらの参照のいずれかを見落とすと、あなたがして終わったと思ったメモリに目に見えない潜んでいるオブジェクトで終わることは非常に簡単です。
厳密にこれらのメモリリークではありません話すが、(それは、すべてのプログラムが終了する前に解放されます)彼らは同じように有害で検出することが困難です。
これらの問題は好都合虚偽の宣言の結果は以下のとおりです。1.あなたは本当にshared_ptrのような単一の所有になりたいものを宣言します。 scoped_ptrを正しいであろうが、そのオブジェクトへの他の参照は、ダングリング残すことができ、生のポインタでなければならないであろう。 2.あなたが本当にshared_ptrのような受動観察参照になりたいものを宣言。 weak_ptrは正しいだろうが、その後、あなたがそれを使用するたびにshare_ptrするためにそれを変換する手間を持っています。
私はあなたのプロジェクトは、この練習はにあなたを得ることができること、トラブルの種類の良い例であると思われる。
あなたは、メモリを集中的に使用するアプリケーションを持っている場合は、あなたのデザインが明示的にオブジェクトの寿命を制御することができるように、あなたは本当に、単一の所有権を必要とします。
単一所有opObject = NULLと;間違いなく、オブジェクトを削除し、それは今それを行います。
の共有所有spObject = NULLと; ........知っている?......
あなたは(たとえば、すべてのアクティブ・インスタンスのリスト、)共有オブジェクトのレジストリを持っている場合は、オブジェクトが解放されることはありません。ソリューション:循環依存関係構造の場合のように(カズドラゴンの答えを参照)、必要に応じてweak_ptrを使用
。スマートポインタはすべてのためにではなく、生のポインタを排除することはできません。
おそらく最悪の危険がshared_ptr
は便利なツールであることから、人々はそれをすべての場所を入れて開始することです。平野ポインタが悪用される可能性があるので、同じ人は生のポインタを狩り、それは意味をなさない場合でも、文字列、コンテナやスマートポインタに置き換えるしようとします。生のポインタの合法的な用途は、容疑者になります。ポインタ警察があります。
これは最悪の危険がないだけで、おそらくですが、それだけで深刻な危険かもしれません。 shared_ptr
のすべての最悪の人権侵害は、スマートポインタは、生のポインタ(どんなことを意味します)に優れている、とどこでもスマートポインタを置くことはC ++プログラミング「より安全」になりますことという考えの直接の結果になります。
operator*
に「暗黙的」であるという事実、operator->
使用反駁する生のポインタに変換する必要があることもちろん単なる事実(または)get()
での明示的な、しかし、暗黙の型変換では、暗黙のではない、これは本当に変換ではないという印象を与えるのに十分であり、この非変換した生のポインタであることを無害の一時ます。
C ++は、「安全な言語」にすることができない、とC ++の有用なサブセットは、「安全な」
ではありませんもちろん安全なサブセットの追求(「メモリ安全」の厳密な意味での「安全」、LISP、ハスケル、Javaの...など)C ++のはの安全なサブセットとして、無限と不満足であることを運命づけられています危険なプリミティブは、ルールではなく例外ではとC ++は、小さな、ほとんど役に立ちません。 C ++での厳格なメモリ安全性にはポインタと自動ストレージ・クラスとの参照のみのことを意味しないでしょう。 <生のポインタを超える他の利点が存在しない場合でもしかし、プログラマは、の定義に信頼されているの言語で、一部の人々は、いくつかの馬鹿プルーフ(原則として)「スマートポインタ」を使用して主張しますEM> のプログラムの状態をネジ止めする一つの特定の方法が回避されます。