web-dev-qa-db-ja.com

オブジェクト全体、またはコンテナへのオブジェクトへのポインタを保存する必要がありますか?

新しいシステムをゼロから設計します。 STLを使用して、特定の長期間有効なオブジェクトのリストとマップを保存します。

質問:オブジェクトのコピーコンストラクターを保持し、STLコンテナー内にオブジェクトのコピーを保存する必要がありますか?それとも、ライフとスコープを自分で管理し、それらのオブジェクトへのポインターをSTLコンテナーに保存する方が一般的に良いですか?

私はこれが詳細にやや短いことを認識していますが、これらの解決策の両方が可能であることを知っているので、「理論的な」より良い答えが存在する場合、私は探しています。

ポインターで遊ぶことの2つの非常に明白な欠点:1)これらのオブジェクトの割り当て/割り当て解除は、STLを超えた範囲で自分で管理する必要があります。 2)スタックに一時オブジェクトを作成して、コンテナに追加できません。

私が不足しているものは他にありますか?

160
Stéphane

人々はポインターを使用することの効率に気づいているので。

Std :: vectorの使用を検討していて、更新が少なく、コレクションを繰り返し処理する場合、オブジェクトの「コピー」を格納する非ポリモーフィックタイプの場合、参照の局所性が向上するため、より効率的です。

Otoh、更新が一般的な場合、ポインターを保存することでコピー/再配置のコストを節約できます。

67

これは本当にあなたの状況に依存します。

オブジェクトが小さく、オブジェクトのコピーを実行するのが軽量である場合、stlコンテナー内にデータを保存することは、ライフタイム管理を心配する必要がないため、私の意見では管理が簡単で簡単です。

オブジェクトが大きく、デフォルトのコンストラクタを使用しても意味がない場合、またはオブジェクトのコピーが高価な場合は、おそらくポインタを使用して保存する方法が適しています。

オブジェクトへのポインタを使用する場合は、 ブーストポインタコンテナライブラリ をご覧ください。このBoostライブラリは、動的に割り当てられたオブジェクトで使用するために、すべてのSTLコンテナーをラップします。

各ポインターコンテナー(ptr_vectorなど)は、コンテナーに追加されたときにオブジェクトの所有権を取得し、それらのオブジェクトの有効期間を管理します。また、ptr_コンテナー内のすべての要素に参照でアクセスします。これにより、次のようなことができます

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.Push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.Push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

これらのクラスはSTLコンテナをラップし、すべてのSTLアルゴリズムで動作します。これは非常に便利です。

コンテナー内のポインターの所有権を呼び出し側に転送する機能もあります(ほとんどのコンテナーのリリース機能を使用)。

46
Nick Haddad

多相オブジェクトを格納している場合は、常に基本クラスポインターのコレクションを使用する必要があります。

つまり、コレクションに異なる派生型を格納する予定がある場合は、ポインターを格納するか、スライスデーモンに食われる必要があります。

38

イベント後3年でジャンプして申し訳ありませんが、注意事項があります...

私の最後の大きなプロジェクトでは、中心的なデータ構造はかなり単純なオブジェクトのセットでした。プロジェクトが開始されて約1年が経ち、要件が進化するにつれて、オブジェクトは実際には多態性である必要があることに気付きました。データ構造を基本クラスポインターのセットに修正し、オブジェクトストレージ、キャスティングなどの付随的な損傷をすべて処理するには、数週間の困難で厄介な脳手術が必要でした。新しいコードが機能していることを確信させるのに数ヶ月かかりました。ちなみに、これにより、C++のオブジェクトモデルがどれほどうまく設計されているかについて考えるのが難しくなりました。

私の現在の大きなプロジェクトでは、中心的なデータ構造はかなり単純なオブジェクトのセットです。プロジェクトに約1年かかった(たまたま今日だった)が、オブジェクトは実際にはポリモーフィックでなければならないことに気付いた。ネットに戻り、このスレッドを見つけ、ニックのBoostポインターコンテナライブラリへのリンクを見つけました。これは、すべてを修正するために前回書いたものとまったく同じなので、今回はそれを試してみます。

とにかく、私にとっての道徳:あなたの仕様が100%石で鋳造されていない場合、ポインタを探してください。

22
EML

両方の長所を最大限に活用してみませんか:スマートポインターのコンテナを実行する( boost::shared_ptr または std::shared_ptr )。メモリを管理する必要はなく、大規模なコピー操作に対処する必要もありません。

19
Branan

通常、オブジェクトをSTLコンテナに直接保存するのが最も簡単で、最も効率的で、オブジェクトを使用するのが最も簡単です。

オブジェクト自体にコピーできない構文がある場合、または抽象基本型の場合は、ポインターを保存する必要があります(最も簡単なのはshared_ptrを使用することです)

11
Greg Rogers

違いをよく把握しているようです。オブジェクトが小さくてコピーしやすい場合は、必ず保存してください。

そうでない場合は、ヒープに割り当てるものへのスマートポインター(ref_countのスマートポインターではない)を格納することを検討します。明らかに、スマートポインターを選択した場合、一時スタックに割り当てられたオブジェクトを格納することはできません(既に述べたように)。

@ Torbjörn はスライスについて良い点を示しています。

3
Lou Franco

コンテナはオブジェクト全体ではなくポインタのみをコピーするため、ポインタの使用はより効率的です。

STLコンテナとスマートポインターに関する有用な情報がここにあります。

標準コンテナでstd :: auto_ptr <>を使用するのはなぜ間違っているのですか?

3
17 of 26

オブジェクトをコード内の他の場所で参照する場合は、boost :: shared_ptrのベクターに格納します。これにより、ベクトルのサイズを変更しても、オブジェクトへのポインターが有効のままになります。

すなわち:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

他に誰もオブジェクトへのポインタを保存しない場合、またはリストが拡大も縮小もしない場合は、単純に古いオブジェクトとして保存します。

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol
2

この質問はしばらくの間私を悩ませてきました。

私はポインターを保存することを好みますが、あなたには当てはまらないかもしれない追加の要件(SWIG luaラッパー)がいくつかあります。

この投稿で最も重要な点は、自分でテストオブジェクトを使用することです

今日、これを実行して、500万回、1000万個のオブジェクトのコレクションでメンバー関数を呼び出す速度をテストしました。

この関数は、xdirとydir(すべてのfloatメンバー変数)に基づいてxとyを更新します。

Std :: listを使用して両方のタイプのオブジェクトを保持しましたが、リストにオブジェクトを保存する方がポインターを使用するよりもわずかに速いことがわかりました。一方、パフォーマンスは非常に近いため、アプリケーションでの使用方法に依存します。

参考までに、ハードウェアで-O3を使用すると、ポインターの完了には41秒かかり、生のオブジェクトの完了には30秒かかりました。

1
Meleneth