36

原則として、C ++ではポインターセマンティクスよりも値を使用することを好みます(つまり、vector<Class>の代わりにを使用しますvector<Class*>)。通常、パフォーマンスのわずかな低下は、動的に割り当てられたオブジェクトを削除することを覚えておく必要がないことで補われます。

残念ながら、すべてが共通ベースから派生するさまざまなオブジェクトタイプを格納する場合、値のコレクションは機能しません。以下の例を参照してください。

#include <iostream>

using namespace std;

class Parent
{
    public:
        Parent() : parent_mem(1) {}
        virtual void write() { cout << "Parent: " << parent_mem << endl; }
        int parent_mem;
};

class Child : public Parent
{
    public:
        Child() : child_mem(2) { parent_mem = 2; }
        void write() { cout << "Child: " << parent_mem << ", " << child_mem << endl; }

        int child_mem;
};

int main(int, char**)
{
    // I can have a polymorphic container with pointer semantics
    vector<Parent*> pointerVec;

    pointerVec.push_back(new Parent());
    pointerVec.push_back(new Child());

    pointerVec[0]->write(); 
    pointerVec[1]->write(); 

    // Output:
    //
    // Parent: 1
    // Child: 2, 2

    // But I can't do it with value semantics

    vector<Parent> valueVec;

    valueVec.push_back(Parent());
    valueVec.push_back(Child());    // gets turned into a Parent object :(

    valueVec[0].write();    
    valueVec[1].write();    

    // Output:
    // 
    // Parent: 1
    // Parent: 2

}

私の質問は次のとおりです。ケーキ(値のセマンティクス)を持って、それも食べることができますか(多形容器)?または、ポインタを使用する必要がありますか?

4

9 に答える 9

26

異なるクラスのオブジェクトは異なるサイズになるため、それらを値として格納すると、スライスの問題が発生することになります。

合理的な解決策の1つは、コンテナーに安全なスマートポインターを格納することです。私は通常、コンテナに安全に保存できるboost::shared_ptrを使用します。std::auto_ptrはそうではないことに注意してください。

vector<shared_ptr<Parent>> vec;
vec.push_back(shared_ptr<Parent>(new Child()));

shared_ptrは参照カウントを使用するため、すべての参照が削除されるまで、基になるインスタンスは削除されません。

于 2008-09-03T02:19:53.260 に答える
12

vector<Foo>は通常vector<Foo*>よりも効率的であることを指摘したいと思います。vector <Foo>では、すべてのFooがメモリ内で互いに隣接します。コールドTLBとキャッシュを想定すると、最初の読み取りでページがTLBに追加され、ベクターのチャンクがL#キャッシュにプルされます。後続の読み取りでは、ウォームキャッシュとロードされたTLBが使用され、キャッシュミスが発生したり、TLBフォールトの頻度が低くなります。

これをvector<Foo*>と比較してください。ベクトルを入力すると、メモリアロケータからFoo*を取得します。アロケータが極端に賢くない場合(tcmalloc?)、または時間の経過とともにベクトルをゆっくりと埋める場合、各Fooの位置は他のFooから遠く離れている可能性があります。おそらく数百バイト、おそらくメガバイト離れています。

最悪の場合、vector <Foo *>をスキャンして各ポインターを逆参照すると、TLBフォールトとキャッシュミスが発生します。これは、vector<Foo>がある場合よりもはるかに遅くなります。(まあ、本当に最悪の場合、各Fooはディスクにページアウトされており、すべての読み取りでディスクのseek()とread()が発生し、ページがRAMに戻されます。)

したがって、必要に応じて、vector<Foo>を使い続けてください。:-)

于 2008-09-16T11:08:33.423 に答える
10

はい、できます。

boost.ptr_container ライブラリは、標準コンテナーのポリモーフィック値セマンティック バージョンを提供します。ヒープに割り当てられたオブジェクトへのポインターを渡すだけで、コンテナーが所有権を取得し、それ以降のすべての操作で値セマンティクスが提供されます。 .

于 2008-09-04T21:39:09.410 に答える
5

boost::anyも検討してください。異種コンテナーに使用しました。値を読み戻すときは、any_cast を実行する必要があります。失敗すると、bad_any_cast がスローされます。そうなると、キャッチして次のタイプに移ることができます。

派生クラスをそのベースにany_castしようとすると、 bad_any_cast がスローされると思います。私はそれを試してみました:

  // But you sort of can do it with boost::any.

  vector<any> valueVec;

  valueVec.push_back(any(Parent()));
  valueVec.push_back(any(Child()));        // remains a Child, wrapped in an Any.

  Parent p = any_cast<Parent>(valueVec[0]);
  Child c = any_cast<Child>(valueVec[1]);
  p.write();
  c.write();

  // Output:
  //
  // Parent: 1
  // Child: 2, 2

  // Now try casting the child as a parent.
  try {
      Parent p2 = any_cast<Parent>(valueVec[1]);
      p2.write();
  }
  catch (const boost::bad_any_cast &e)
  {
      cout << e.what() << endl;
  }

  // Output:
  // boost::bad_any_cast: failed conversion using boost::any_cast

そうは言っても、最初に shared_ptr ルートにも行きます! これはちょっと興味があるかもしれないと思っただけです。

于 2008-09-03T02:56:35.607 に答える
2

static_castreinterpret_castを見てください
。C++プログラミング言語第3版では、Bjarne Stroustrupが130ページで説明しています。これについては、第6章にセクション全体があります。
親クラスを子クラスに再キャストできます。これには、それぞれがいつであるかを知る必要があります。この本の中で、ストロヴルプ博士はこの状況を回避するためのさまざまなテクニックについて語っています。

こんなことしないで。これは、そもそも達成しようとしているポリモーフィズムを打ち消します!

于 2008-09-03T02:29:26.340 に答える
2

すでに述べた1800の情報すべてに1つ追加するだけです。

この問題をよりよく理解するために、Scott Mayersによる「より効果的なC++」 「項目3:配列を多態的に扱わないでください」を参照することをお勧めします。

于 2008-09-03T07:36:41.860 に答える
2

ほとんどのコンテナー タイプは、リンク リスト、ベクター、ツリー ベースなど、特定のストレージ戦略を抽象化したいと考えています。このため、前述のケーキを所有することと消費することの両方で問題が発生します (つまり、ケーキは嘘です (注: 誰かがこの冗談を言わなければなりませんでした))。

じゃあ何をすればいいの?いくつかのかわいいオプションがありますが、ほとんどはいくつかのテーマの 1 つまたはそれらの組み合わせのバリエーションに縮小されます: 適切なスマート ポインターを選択または発明する、テンプレートまたはテンプレート テンプレートを巧妙な方法で操作する、コンテナーの共通インターフェイスを使用するコンテナごとの二重ディスパッチを実装するためのフックを提供します。

宣言された 2 つの目標の間には基本的な緊張関係があります。そのため、何が欲しいかを決めてから、基本的に自分が望むものを実現できるものを設計するようにしてください。巧妙な参照カウントとファクトリの十分に巧妙な実装を使用して、値のように見えるポインタを取得するために、いくつかの素晴らしく予想外のトリックを行うことができます。基本的な考え方は、参照カウントとコピー オン デマンドと constness、および (要因として) プリプロセッサ、テンプレート、および C++ の静的初期化規則の組み合わせを使用して、ポインタ変換の自動化について可能な限りスマートなものを取得することです。

私は過去に、C++ での値セマンティック プログラミングの基礎のようなものを達成するために、Virtual Proxy / Envelope-Letter / 参照カウント ポインターを使用したかわいいトリックを使用する方法を想像するのに時間を費やしました。

そして、それは可能だと思いますが、C++ 内にかなり閉じた C# マネージド コードのような世界を提供する必要があります (ただし、必要に応じて、基礎となる C++ にブレークスルーすることができます)。ですから、あなたの考え方にはとても共感できます。

于 2008-09-03T02:58:41.920 に答える
1

公開された値型のセマンティクスを持つ独自のテンプレート化されたコレクション クラスを使用していますが、内部的にポインターを格納しています。逆参照時にポインタではなく値参照を取得するカスタム イテレータ クラスを使用しています。コレクションをコピーすると、ポインターが重複するのではなく、アイテムの深いコピーが作成されます。これが、ほとんどのオーバーヘッドが存在する場所です (私が代わりに得るものを考えると、非常に小さな問題です)。

それはあなたのニーズに合ったアイデアです。

于 2008-09-16T11:24:19.470 に答える