2013-07-08 2 views
16

は、以下のクラスポリモーフィックオブジェクトのコレクションを所有するのに好ましいC++イディオムは何ですか?

class Base { 
public: 
    virtual void do_stuff() = 0; 
}; 

class Derived : public Base { 
public 
    virtual void do_stuff() { std::cout << "I'm useful"; }; 
}; 

を考えてみましょう今度は、私はBase年代派生型のオブジェクトを所有する責任別のクラスを持っているし、彼らのdo_stuff()メソッドを呼び出して、それらを反復処理したいとしましょう。それはこのように見えますが、私はいくつかの可能性を参照してください

class Owner { 
public: 
    void do_all_stuff() { 
     //iterate through all items and call do_stuff() on them 
    } 

    void add_item(T item) { 
     items.push_back(item); 
    } 

    vector<T> items; 
} 

として Tが宣言されなければならないのか分からない。私は唯一のオブジェクトを追加することができるだろうので、

Tは、Baseすることはできません具体的なタイプのBaseのように、それは問題外です。

TBase*またはBase&することができますが、今私は私にポインタまたは私はitemsからそれを取得する際にまだ存在するオブジェクトへの参照を渡すためにadd_item()の呼び出し元を信頼する必要があります。私はのデストラクタの要素をdeleteにすることはできません。なぜなら、私はそれらが動的に割り当てられているのか分からないからです。しかし、それらがあれば、彼らはdelete 'dでなければなりません。それは私にあいまいな所有権を残します。

TBase*またはBase&することができ、私はOwnerBase* create_item<DerivedT>() { return new DerivedT; }メソッドを追加します。この方法では、私はポインタが有効なままと私はそれを所有していることを知っているが、私はDerivedTで非デフォルトのコンストラクタを呼び出すことができません。また、Ownerは、オブジェクトのインスタンス化も担当します。 Ownerのデストラクタのすべての項目を削除する必要がありますが、これは大きな問題ではありません。

基本的に、私は次のように何かをできるようにしたいと思います:

Owner owner; 

void add_one() { 
    Derived d; 

    owner.add_item(d); 
} 

void ready() { 
    owner.do_all_stuff(); 
} 

void main() { 
    for(int i = 0; i < 10; ++i) { 
     add_one(); 
    } 
    ready(); 
} 

私はそこに意味を移動するために関連する何かがあると確信している(私はそれらを所有するadd_items()に渡されたオブジェクトを移動することができます)しかし私はまだ私のコレクションが宣言される方法を把握することができません。

この種のポリモーフィックオーナーシップ(特にSTLコンテナ)のC++イディオムは何ですか?

+1

多態性が必要な場合は、最終的に 'std :: container '、 "ambiguous"を実行するかどうかが決まります。メモリ管理が心配な方は、コンテナ>を作成することもできます。 –

+8

あなたは多種多様な選択肢を持っていません。あなたは、(a)生ポインタ(b)スマートポインタのコレクションを持つことができます。それはそれです。理論では、(a)は、(a.1)コンテナが所有するオブジェクトへの生ポインタと(a.2)コンテナが所有していないオブジェクトへの生ポインタにさらに細分することができます。あなたはコインを投げて決定するだけです。いずれかの顔に寝る場合は、(b)を使用します。立って休むことになるなら、(a.1)を使ってください。空気中に浮遊している場合は、(a.2)を使用してください。簡単ですね。 –

+1

@ n.m。 LOL ...... –

答えて

18

多相オブジェクトは、ポインタまたは参照によって処理する必要があります。彼らの生涯はおそらく特定のスコープに束縛されていないので、おそらくダイナミックストレージの持続時間を持ちます。つまり、スマートポインタを使用する必要があります。

std::shared_ptrstd::unique_ptrなどのスマートポインタは、標準コレクションタイプでうまく機能します。Ownerでこれを使用して

std::vector<std::unique_ptr<Base>> 

次のようになります。add_itemへの引数の型は、項目を追加するために必要な所有権のポリシーを識別し、それを台無しにするために彼らの方法の外に出るようにユーザーに要求する

class Owner { 
public: 
    void do_all_stuff() { 
     //iterate through all items and call do_stuff() on them 
    } 

    void add_item(std::unique_ptr<Base> item) { 
     items.push_back(std::move(item)); 
    } 

    vector<std::unique_ptr<Base>> items; 
} 

。例えば、unique_ptrには明示的なコンストラクタがあるので、暗黙的で互換性のない所有権セマンティクスを持つ生のポインタを誤って渡すことはできません。

unique_ptrも、Ownerが所有するオブジェクトを削除します。 Baseに仮想デストラクタがあることを確認する必要がありますが。あなたの現在の定義で、未定義の動作が得られます。多相オブジェクトは、常に仮想デストラクタを持つ必要があります。 (unique_ptrがあなたのC++ 11可用性に応じてブーストやSTDどこから来る)Ownerが含まれているオブジェクトの唯一所有者であることを、あなたの文脈から想定すると

+0

偉大な答えをありがとう。 'unique_ptr'をコピーすることができないので、' items.push_back(std :: move(item)); 'でなければならないことを将来の参考として指摘したいと思います。 –

+0

また、emplace_backを私のデフォルトにするべきです。しかし、私が正しく覚えていれば動いても同等です。 – Klaim

+0

@Klaimこの場合、 'push_back'と実際的な違いはありません。 –

1

実際には、ポインタや実際の値だけをSTLに格納することはできません。 だからTはベース* コンパイラに不満を持たせる他のことを試してみてください。

+0

実際には、単純な 'Base * 'ではなく、' shared_ptr 'または' unique_ptr 'をコンテナに入れたいと思うでしょう。 –

+0

状況によって異なります。それらは実行可能なオプションです。 –

+2

別のオプションは、reference_wrapper です。 – bstamour

2

Tunique_ptr<Base>でなければなりません。これは、コンテナによって所有されていることを正しく認識しており、add_itemコールで所有権移転セマンティクスをさらに示しています。検討する価値

+0

実際、コンテナでは、 'unique_ptr'ではなく' std :: shared_ptr'を使う方が良いでしょう。 –

+4

@ James Kanzeなぜあなたは、単独の所有者コンテナで 'shared_ptr'の追加オーバーヘッドを好むのですか? –

+3

@JamesKanzeなぜですか? 'unique_ptr'も私の最初の選択です。 'unique_ptr'(おそらく)は、より良い実行と少ないメモリ使用と同期を強制しないことに加えて、より正確に所有権セマンティクスを反映します。 – bames53

2

他の選択肢は利用するために、あなたの多型のタイプのadobe::polyまたはboost::type_erasureのようなライブラリを使用し、boost::ptr_container、またはより良いを使用するようにしている価値ベースの実行時ポリモーフィズム-回避などのポインタ、相続、

の必要性は、
関連する問題