3

基本クラスへのポインターの配列があるので、これらのポインターが基本クラスの (異なる) サブクラスを指すようにしますが、それらと対話することができます。(実際には、仮想化してオーバーロードしたメソッドは2、3しかありません)ポインターの使用を避け、代わりに基本クラスの配列を作成するだけで、クラスを私のサブクラスに設定する方法があるかどうか疑問に思っています選ぶ。仮想メソッドの関数ポインターを検索するためにそれを使用する必要があるため、クラスを指定する何かがそこにあるに違いないことはわかっています。ところで、サブクラスはすべて同じ ivar とレイアウトを持っています。

注: 設計は実際には、パフォーマンスの向上のために変数の代わりにテンプレート引数を使用することに基づいているため、実際には抽象基本クラスはサブクラスの単なるインターフェイスであり、コンパイルされたコードを除いてすべて同じです。

ありがとう

編集:すべてのサブクラス(および必要に応じて)ベースクラスは同じレイアウト/サイズを持っています

それはさておき、戦略パターンは良いでしょうが、それはクラスへのポインターを追加しますが、私が避けようとしているのはまさに1つの違いです.

簡単に言えば、私がやろうとしていることは

class base {
int ivars[100];
public:
virtual char method() = 0;
}

template <char c>
class subclass : base {
public:
  char method() {
    return c;
  }
}

base things[10];
some_special_cast_thingy(things[2], subclass);
printf("%c", things[2].method());

明らかにそれははるかに複雑ですが、クラスに関する限り、私が探しているのはほとんどすべてです。これはおそらく言語機能です。

4

5 に答える 5

2

ポインターとポリモーフィズムを使用してください。それがその目的です。多少のオーバーヘッドはありますが、非常に要求の厳しい環境でない限り、ごくわずかです。これにより、後で新しいサブクラスを追加するための柔軟性が大幅に向上します。

于 2010-03-27T22:42:55.033 に答える
2

発生している問題は、ストレージの割り当てに関係しています。配列が割り当てられるとき、配列にはすべての要素のストレージが含まれている必要があります。(非常に単純化された)例を挙げましょう。次のように設定されたクラスがあるとします。

class Base
{
public:
  int A;
  int B;
}

class ChildOne : Base
{
public:
  int C;
}

class ChildTwo : Base
{
public:
  double C;
}

を割り当てるとBase[10]、配列内の各要素には (一般的な 32 ビット システム* で) 8 バイトのストレージが必要になります。これは、2 つの 4 バイト int を保持するのに十分です。ただし、ChildOneクラスには、その親の 8 バイトのストレージに加えて、そのメンバー用にさらに 4 バイトが必要ですC。クラスには、その親のChildTwo8 バイトに加えて、そのdouble C. これら 2 つの子クラスのいずれかを、8 バイトに割り当てられた配列にプッシュしようとするとBase、ストレージがオーバーフローしてしまいます。

ポインターの配列が機能する理由は、それらが何を指しているかに関係なく、一定のサイズ (32 ビット システムではそれぞれ 4 バイト) であるためです。a へのポインターBaseは、 a へのポインターと同じですがChildTwo、後者のクラスは 2 倍のサイズです。

演算子を使用すると、dynamic_castタイプセーフなダウンキャストを実行して を に変更できるBase*ためChildTwo*、この特定のケースで問題が解決します。

または、次のようなクラス レイアウトを作成することで、データ ストレージ (戦略パターン)から処理ロジックを切り離すことができます。

class Data
{
public:
  int A;
  int B;

  Data(HandlerBase* myHandler);
  int DoSomething() { return myHandler->DoSomething(this) }
protected:
  HandlerBase* myHandler;
}

class HandlerBase
{
public:
  virtual int DoSomething(Data* obj) = 0;
}

class ChildHandler : HandlerBase
{
public:
  virtual int DoSomething(Data* obj) { return obj->A; }
}

このパターンは、 のアルゴリズム ロジックが、DoSomething多数のオブジェクトに共通する重要なセットアップまたは初期化を必要とする可能性がある場合 (およびChildHandler構築で処理できる場合) に適していますが、普遍的ではありません (したがって、静的メンバーには適していません)。 . その後、データ オブジェクトは一貫したストレージを維持し、操作を実行するために使用されるハンドラー プロセスをポイントし、何かを呼び出す必要がある場合に自身をパラメーターとして渡します。Dataこの種のオブジェクトは一貫した予測可能なサイズを持ち、配列にグループ化して参照の局所性を維持できますが、通常の継承メカニズムのすべての柔軟性も備えています。

ただし、ポインターの配列に相当するものをまだ構築していることに注意してください。それらは、実際の配列構造の奥深くにある別のレイヤーに配置されているだけです。

* 細かいことを言う人へ: はい、記憶領域の割り当てに指定した数値は、クラス ヘッダー、vtable 情報、パディング、およびその他の潜在的なコンパイラに関する多数の考慮事項を無視していることに気付きました。これは網羅的なものではありませんでした。

パート II の編集: 以下の資料はすべて誤りです。私はそれをテストせずに頭のてっぺんから投稿し、2 つの無関係なポインターを reinterpret_cast する機能と、2 つの無関係なクラスをキャストする機能を混同しました。私の過ちを指摘してくれた Charles Bailey に感謝します

配列からオブジェクトを強制的に取得し、それを別のクラスとして使用することはできますが、オブジェクト アドレスを取得し、ポインタを新しいオブジェクト型に強制的にキャストする必要があり、理論上の目的を無効にします。ポインターの逆参照を回避します。いずれにせよ、私の当初の主張 -- これは最初にしようとしているのは恐ろしい「最適化」です -- は今でも有効です。

編集:さて、あなたの最新の編集で、あなたが何をしようとしているのか理解できたと思います. ここで解決策を提供しますが、すべての聖なる愛のために、これを本番コードで決して使用しないことを誓ってください。これはエンジニアリングの好奇心であり、良い習慣ではありません。

ポインターの逆参照を回避しようとしているようですが (おそらくパフォーマンスのマイクロ最適化として?)、オブジェクトに対してサブメソッドを呼び出す柔軟性が必要です。基本クラスと派生クラスが同じサイズであることが確実にわかっている場合、これを知る唯一の方法は、コンパイラによって生成された物理クラスのレイアウトを調べることです。コンパイラは、必要に応じてあらゆる種類の調整を行うことができるからです。 、そして仕様はあなたに何の保証も与えません - そして reinterpret_cast を使用して、親を配列内の子として強制的に扱うことができます。

class Base
{
public:
  int A;
  int B;

  void DoSomething();
}

class Derived : Base
{
  void DoSomething();
}

void DangerousGames()
{
  // create an array of ten default-constructed Base on the stack
  Base items[10];
  // force the compiler to treat the bits of items[5] as a Derived,
  // and make a ref
  Derived& childItem = reinterpret_cast<Derived>(items[5]);
  // invoke Derived::DoSomething() using the data bits of items[5], 
  // since it has an identical layout
  childItem.DoSomething();
}

これにより、ポインターの逆参照が節約され、パフォーマンスが低下することはありません。reinterpret_cast はランタイム キャストではないためです。これは本質的にコンパイラのオーバーライドであり、「あなたが何を知っていると思っていても、私は自分が何をしているのか知っています。黙って、やれ。" 「わずかな欠点」は、コードが非常に脆弱になることです。これは、またはコンパイラによって開始されたかどうかに関係なく、Baseまたはのレイアウトに変更を加えるとDerived、全体が炎上してクラッシュする可能性が高いためです。非常に微妙で、未定義の動作をデバッグすることはほとんど不可能です。繰り返しますが、これを本番コードで使用しないでください。最もパフォーマンスが重要なリアルタイム システムでさえ、ポインター逆参照のコストは常にコードベースの真ん中に核爆弾に相当するものを構築するよりも価値があります。

于 2010-03-27T23:18:04.620 に答える
1

これは、状態パターンまたは戦略パターンのように聞こえます。

クラスからデータを分離し、配列にデータと基本クラスへのポインターを含めます。必要に応じて別のサブクラスを指すようにポインターを設定しますが、基本クラスのメソッドは配列の要素へのポインターまたは参照を使用してデータにアクセスします。サブクラスは基本クラスのメソッドを実装しますが、メソッドに渡されるもの以外のデータはありません。

class Base;

class Data{
  string name;
  int age;
  Base* base;
};

class Base{
  virtual void method1(Data&)=0;
  // other methods
};

class Subclass1: public Base{
  void method1(Data& data){ data.age=0; }
};

vector<Data> dataVector;
Subclass1 subclass1;
Data* someData = ...
someData->base=&subclass1;

someData->base->method1(*someData);
于 2010-03-27T22:55:03.333 に答える
0

ポインターの配列を使用できます。派生クラス インターフェイスの一部にアクセスしたい場合は、dynamic_cast を使用できます。http://en.wikipedia.org/wiki/Dynamic_cast

于 2010-03-27T22:46:41.093 に答える
0

コンパイラはすべての要素の正確な位置を計算できる必要があるため、配列は正確な型である必要があります。したがって、サブクラスを混在させたり、サブクラスの配列を基本クラスの配列として扱うことはできません。

于 2010-03-27T22:48:59.057 に答える