18

次のクラス構造があるとします。

class Car;
class FooCar : public Car;
class BarCar : public Car;

class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;

Carその へのハンドルも与えましょうEngine。はFooCarで作成されFooEngine*BarCarは で作成されBarEngine*ます。オブジェクトがダウンキャストせずにFooCarメンバー関数を呼び出せるように配置する方法はありますか?FooEngine

クラス構造が現在のようにレイアウトされている理由は次のとおりです。

  1. すべてCarの にはEngine. さらに、 aFooCarは a のみを使用しFooEngineます。
  2. Engineコピーして貼り付けたくない、すべてのユーザーが共有するデータとアルゴリズムがあります。
  3. Engineがその について知る必要がある関数を書きたいと思うかもしれませんCar

dynamic_castこのコードを書いているときに入力するとすぐに、何か間違ったことをしている可能性があることがわかりました。これを行うより良い方法はありますか?

アップデート:

これまでの回答に基づいて、私は次の 2 つの可能性に傾いています。

  1. 純粋Carな仮想getEngine()関数を提供する必要があります。これにより、正しい種類の を返す実装が可能FooCarになります。BarCarEngine
  2. Engineすべての機能をCar継承ツリー に吸収します。Engineメンテナンス上の理由で(Engine別の場所に保管するため)壊れていました。これは、小さなクラス (コード行が小さい) を増やすか、大きなクラスを減らすかのトレードオフです。

これらのソリューションのいずれかに対するコミュニティの強い好みはありますか? 私が考慮していない 3 番目のオプションはありますか?

4

10 に答える 10

24

Carがエンジンポインターを保持していると思います。そのため、ダウンキャストしていることに気づきます。

基本クラスからポインターを取り出し、純粋な仮想get_engine()関数に置き換えます。次に、FooCarとBarCarは正しいエンジンタイプへのポインターを保持できます。

(編集)

これが機能する理由:

仮想関数は参照またはポインターCar::get_engine()を返すため、C ++では、戻り型がより派生型であるという点でのみ異なる限り、派生クラスがこの関数を異なる戻り型で実装できるようになります。

これは共変リターンタイプと呼ばれ、各Carタイプが正しいを返すことができますEngine

于 2009-01-19T22:58:32.613 に答える
10

私が付け加えたかったことの1つは、このデザインは、私が平行木と呼んでいるもののために、すでに私には悪臭を放っています。

基本的に、(CarとEngineの場合のように)並列クラス階層になってしまう場合は、問題を抱えているだけです。

Engine(およびCar)にサブクラスが必要な場合、またはそれらがすべて同じそれぞれの基本クラスの異なるインスタンスである場合は、再考します。

于 2009-01-19T22:57:28.247 に答える
7

次のように Engine タイプをテンプレート化することもできます

template<class EngineType>
class Car
{
    protected:
        EngineType* getEngine() {return pEngine;}
    private:
        EngineType* pEngine;
};

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>
于 2009-01-20T01:37:16.567 に答える
4

車をエンジンで構成できない理由がわかりません(BarCarに常にBarEngineが含まれている場合)。エンジンは車とかなり強い関係があります。を好む:

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}
于 2009-01-19T23:06:55.903 に答える
2

FooEngineはFooCarに、BarEngineはBarCarに保存できます

class Car {
public:
  ...
  virtual Engine* getEngine() = 0;
  // maybe add const-variant
};

class FooCar : public Car
{
  FooEngine* engine;
public:
  FooCar(FooEngine* e) : engine(e) {}
  FooEngine* getEngine() { return engine; }
};

// BarCar similarly

このアプローチの問題は、エンジンの取得が仮想呼び出しであり(それが心配な場合)、エンジンを設定Carする方法ではダウンキャストが必要になることです。

于 2009-01-19T23:02:47.067 に答える
2

FooCar が BarEngine を使用することは可能でしょうか?

そうでない場合は、AbstractFactory を使用して、適切なエンジンで適切な車のオブジェクトを作成することをお勧めします。

于 2009-01-19T22:49:55.767 に答える
1

Engineとその子によってのみプライベートに使用されるCarか、他のオブジェクトでも使用するかによって異なると思います。

機能がs にEngine固有のものでない場合は、基本クラスにポインターを保持する代わりにメソッドを使用します。Carvirtual Engine* getEngine()

そのロジックがCars に固有のものである場合、共通のEngineデータ/ロジックを別のオブジェクト (必ずしもポリモーフィックではない)に配置し、それぞれの子クラスに保持FooEngineして実装することをお勧めします。BarEngineCar

インターフェイスの継承よりも実装の再利用が必要な場合は、多くの場合、オブジェクト合成の方が柔軟性が高くなります。

于 2009-01-20T03:35:54.940 に答える
1

Microsoft の COM はちょっと不格好ですが、斬新なコンセプトを持っています。オブジェクトのインターフェイスへのポインターがある場合、QueryInterface関数を使用して他のインターフェイスをサポートしているかどうかを照会できます。Engine クラスを複数のインターフェースに分割して、それぞれを独立して使用できるようにするという考え方です。

于 2009-01-20T04:24:23.267 に答える
0

私が何かを見逃していないことを許すと、これはかなり些細なことです。

これを行う最良の方法は、エンジンで純粋仮想関数を作成することです。これは、インスタンス化される派生クラスで必要になります。

追加のクレジットソリューションは、おそらくIEngineと呼ばれるインターフェイスを使用することです。このインターフェイスは、代わりにCarに渡され、IEngineのすべての関数は純粋な仮想です。必要な機能の一部を実装する「BaseEngine」(つまり、「共有」)を作成し、それからリーフ機能を作成することができます。

エンジンのように「見せたい」ものがあれば、インターフェースは素晴らしいですが、おそらくそうではありません(つまり、模擬テストクラスなど)。

于 2009-01-19T22:54:20.733 に答える
0

FooCarオブジェクトがダウンキャストせずにFooEngineのメンバー関数を呼び出すことができるように物事を調整する方法はありますか?

このような:

class Car
{
  Engine* m_engine;
protected:
  Car(Engine* engine)
  : m_engine(engine)
  {}
};

class FooCar : public Car
{
  FooEngine* m_fooEngine;
public:
  FooCar(FooEngine* fooEngine)
  : base(fooEngine)
  , m_fooEngine(fooEngine)
  {}
};
于 2009-01-19T22:56:09.310 に答える