web-dev-qa-db-ja.com

boost :: shared_ptrを使用する場合の潜在的な危険は何ですか?

boost::shared_ptr を使用するときに足で自分を撃つことができるいくつかの方法は何ですか?言い換えれば、 boost::shared_ptr を使用するときに回避しなければならない落とし穴は何ですか?

48
Frank

循環参照:元のオブジェクトへの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つの強い参照を失い、クリーンアップなどが発生します。要するに、これは漏れません。そして、shared_ptr <>をweak_ptr <>に戦略的に置き換えるだけです。

注:上記は、boost :: shared_ptr <>およびboost :: weak_ptr <>の場合と同様に、std :: shared_ptr <>およびstd :: weak_ptr <>にも同様に適用されます。

42
Kaz Dragon

同じオブジェクトに複数の無関係な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;
}
25
Michael Burr

たとえば、関数呼び出しの引数内に、匿名の一時共有ポインタを作成します。

_f(shared_ptr<Foo>(new Foo()), g());
_

これは、new Foo()を実行してから、g()を呼び出し、g()を呼び出して、_shared_ptr_なしで例外をスローすることが許可されているためです。設定されているため、_shared_ptr_にはFooオブジェクトをクリーンアップする機会がありません。

18
Brian Campbell

同じオブジェクトへの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への参照を渡しました。保存する前にコピーされていることを確認してください(つまり、クラスメンバーとしての参照はありません)。

13
Robert

避けるべき2つのことがあります:

  • get()関数を呼び出して、生のポインターを取得し、ポイントされたオブジェクトがスコープ外になった後にそれを使用します。

  • shared_ptrの参照または生のポインタを渡すことも危険です。オブジェクトを存続させるのに役立つ内部カウントをインクリメントしないためです。

12
Frank

数週間の奇妙な動作をデバッグします。

理由は:
「shared_from_this」の代わりに「this」を一部のスレッドワーカーに渡しました。

9
Mykola Golubyev

正確にはフットガンではありませんが、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の便利な関数に既に精通している場合は、ある程度慣れる必要があります。

このDDJの記事 多くのサンプルコードを使用して、主題に触れています。私も ブログ 数年前に最初にそれを行う方法を理解しなければならなかったとき。

ヒープ上に多数の小さなオブジェクトがあるが、それらが実際には「共有」されていない場合、非常に小さなオブジェクト(charshortなど)にshared_ptrを使用するとオーバーヘッドになる可能性があります。 boost::shared_ptrは、Boost1.42を使用するg ++ 4.4.3およびVS2008で作成する新しい参照カウントごとに16バイトを割り当てます。 std::tr1::shared_ptrは20バイトを割り当てます。ここで、100万個の異なるshared_ptr<char>がある場合、これは、2,000万バイトのメモリがcount = 1だけを保持していることを意味します。間接コストとメモリの断片化は言うまでもありません。お気に入りのプラットフォームで以下を試してください。

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);
}
3
Sumant

マルチスレッドコードでshared_ptrを使用する場合は、注意が必要です。その場合、同じメモリを指す2つのshared_ptrが異なるスレッドによって使用される場合、比較的簡単にケースに入ることができます。

1
oo_olo_oo

クラス定義内でこれにshared_ptr <T>を与えることも危険です。代わりにenabled_shared_from_thisを使用してください。

次の投稿を参照してください ここ

1
George Godik

Shared_ptrの一般的な広範な使用は、ほとんど必然的に、望ましくない、目に見えないメモリ占有を引き起こします。

循環参照はよく知られている原因であり、それらのいくつかは間接的であり、特に複数のプログラマーが作業する複雑なコードでは見つけるのが難しい場合があります。プログラマーは、あるオブジェクトが別のオブジェクトへの参照を必要としていると判断する場合があり、すべてのコードを調べてサイクルを閉じているかどうかを確認する時間がありません。この危険は非常に過小評価されています。

あまりよく理解されていないのは、リリースされていない参照の問題です。オブジェクトが多くのshared_ptrsに共有されている場合、それらのすべてがゼロになるかスコープから外れるまで、オブジェクトは破棄されません。これらの参照の1つを見落とし、メモリ内に見えないオブジェクトが潜んでいて、終了したと思ってしまうことは非常に簡単です。

厳密に言えば、これらはメモリリークではありませんが(プログラムが終了する前にすべて解放されます)、同様に有害であり、検出が困難です。

これらの問題は、適切な誤った宣言の結果です。1。実際に単一の所有権になりたいものをshared_ptrとして宣言します。 scoped_ptrは正しいですが、そのオブジェクトへの他の参照は生のポインターである必要があり、ぶら下がったままになる可能性があります。 2.パッシブ監視参照になりたいものをshared_ptrとして宣言します。 weak_ptrは正しいでしょうが、それを使用するたびにそれをshare_ptrに変換する手間がかかります。

あなたのプロジェクトは、この慣習があなたを巻き込む可能性のある種類の問題の良い例だと思います。

メモリを大量に消費するアプリケーションを使用している場合は、デザインがオブジェクトの有効期間を明示的に制御できるように、単一の所有権が本当に必要です。

単一所有権の場合opObject = NULL;間違いなくオブジェクトを削除し、今それを行います。

共有所有権の場合spObject = NULL; ........知るか?......

0
John Morrison