web-dev-qa-db-ja.com

C ++:オブジェクトのベクトルvs新しいオブジェクトへのポインターのベクトル?

サンプルソフトウェアレンダラーを作成して、C++のスキルを向上させたいと考えています。 3Dスペース内のポイントで構成されるオブジェクトを取得して2Dビューポートにマッピングし、ビュー内の各ポイントにさまざまなサイズの円を描画します。どちらが良いですか:

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.Push_back(DerivedClass1());
        object_list.Push_back(DerivedClass2());

または...

class World{
    vector<ObjectBaseClass*> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.Push_back(new DerivedClass1());
        object_list.Push_back(new DerivedClass2());

??ベクトルは最初の例では自動的にDerivedClassデストラクタを呼び出しますが2番目の例では呼び出さないため、2番目の例でポインタを使用して新しいオブジェクトを作成すると、ベクトルを使用するポイントが無効になりますか?アクセス方法を使用する限り、ベクターはメモリ管理を処理するため、ベクターを使用するときに新しいオブジェクトへのポインターは必要ですか?今、私が世界に別の方法を持っているとしましょう:

void drawfrom(Viewport& view){
    for (unsigned int i=0;i<object_list.size();++i){
        object_list.at(i).draw(view);
    }
}

これが呼び出されると、ワールドリスト内のすべてのオブジェクトに対してdrawメソッドが実行されます。派生クラスが独自のバージョンのdraw()を持つことができるようにしたいとします。メソッドセレクター(->)を使用するには、リストをポインターにする必要がありますか?

27
metamemetics

あなたはこのコードであなたが望むものを手に入れません

class World{
    vector<ObjectBaseClass> object_list;
public:
    void generate(){
        object_list.clear();
        object_list.Push_back(DerivedClass1());
        object_list.Push_back(DerivedClass2());

何が起こるかは、オブジェクトスライスと呼ばれます。 ObjectBaseClassのベクターを取得します。

ポリモーフィズムを機能させるには、ある種のポインタを使用する必要があります。おそらくいくつかのスマートポインターまたは参照がboostまたは他のライブラリーにあり、使用してコードを2番目に提案されたソリューションよりもはるかに安全にすることができます。

19
Maciej Hehl

C++を改善することを明示的に述べているので、 Boost の使用を開始することをお勧めします。これにより、3つの異なる方法で問題を解決できます。

shared_ptrの使用

shared_ptr を使用すると、次のようにベクターを宣言できます。

std::vector< boost::shared_ptr< ObjectBase > > object_list;

次のように使用します。

typedef std::vector< boost::shared_ptr< ObjectBase > >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

これはポリモーフィズムを提供し、通常のポインターのベクトルと同じように使用されますが、shared_ptrはメモリ管理を行い、オブジェクトを参照している最後のshared_ptrが破棄されるとオブジェクトを破棄します。

C++ 11に関する注意:C++ 11では、shared_ptrstd::shared_ptrとして規格の一部になったため、Boostはこのアプローチではもはや必要ありません。ただし、共有所有権が本当に必要な場合を除き、C++ 11で新しく導入されたstd::unique_ptrを使用することをお勧めします。

ptr_vectorの使用

ptr_vector を使用すると、次のようになります。

boost::ptr_vector< ObjectBase > object_list;

次のように使用します。

typedef boost::ptr_vector< ObjectBase >::iterator ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    (*it)->draw(view);

これもポインタの通常のベクトルのように使用されますが、今回はptr_vectorがオブジェクトの寿命を管理します。最初のアプローチとの違いは、ベクターが破棄されるとオブジェクトが破棄されることですが、オブジェクトを参照する他のshared_ptrsが存在する場合、それらはコンテナーよりも長く存続する可能性があります。

reference_wrapperの使用

reference_wrapper を使用して、次のように宣言します。

std::vector< boost::reference_wrapper< ObjectBase > > object_list;

そして、次のように使用します。

typedef std::vector< boost::reference_wrapper< ObjectBase > >::iterator 
    ObjectIterator;

for ( ObjectIterator it = object_list.begin(); it != object_list.end(); it++ )
    it->draw(view);

上記のアプローチのように、最初にイテレータを逆参照する必要がないことに注意してください。ただし、これが機能するのは、オブジェクトの有効期間が別の場所で管理されており、vectorよりも長いことが保証されている場合のみです。

C++ 11に関する注意:reference_wrapperもC++ 11で標準化され、std::reference_wrapperとして使用できるようになりました。ブースト。

Maciej H sの回答で指摘されているように、最初のアプローチでは object slicing になります。一般に、コンテナーを使用する場合は、 iterators を確認する必要があります。

27
Björn Pollex

最初の質問に関しては、動的に割り当てられたオブジェクト(つまり、ポインタを格納するためにnot)ではなく、自動的に割り当てられたオブジェクトを使用することが一般的に望ましい問題のタイプについては、コピーの構築と割り当てが可能であり、法外に高価ではありません。

オブジェクトをコピーまたは割り当てることができない場合は、とにかくそれらを直接std::vectorに入れることはできないため、問題は疑わしいものです。コピー操作や割り当て操作に負荷がかかる場合(オブジェクトに大量のデータが格納される場合など)は、効率上の理由からポインターを格納することをお勧めします。それ以外の場合は、一般的に、あなたが言及した理由に基づいてポインタを保存しない方がよい(自動割り当て解除)

2番目の質問については、はい、それがポインタを格納するもう1つの有効な理由です。動的ディスパッチ(仮想メソッド呼び出し)は、ポインターと参照でのみ機能します(参照をstd::vectorに格納することはできません)。複数の多相型のオブジェクトを同じベクターに格納する必要がある場合は、スライスを回避するためにポインターを格納する必要があります。

13
Tyler McHenry

まあ、それはあなたがあなたのベクトルで何をしようとしているのかに依存します。

ポインターを使用しない場合は、渡したオブジェクトのcopyがベクターに配置されます。それが単純なオブジェクトである場合、および/またはそれらのストレージを追跡することに煩わされたくない場合、これはまさにあなたが望むものであるかもしれません。複雑である場合、または構築と破棄に非常に時間がかかる場合は、その作業をそれぞれ1回だけ行い、ポインターをベクターに渡すことをお勧めします。

2
T.E.D.