C ++コードでdynamic_castを回避するにはどうすればよいですか?
-
19-08-2019 - |
質問
次のクラス構造があるとしましょう:
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*
で作成されます。 FooEngine
オブジェクトがダウンキャストせずにdynamic_cast
のメンバー関数を呼び出せるように物事を整理する方法はありますか?
クラス構造が現在の方法でレイアウトされる理由は次のとおりです。
- すべての
getEngine()
には<=>があります。さらに、<=>は<=>のみを使用します。 - すべての<=>で共有されるデータとアルゴリズムがあり、コピーアンドペーストしたくない。
- <=>を<=>について知る必要がある関数を記述したい場合があります。
このコードを書くときに<=>と入力するとすぐに、おそらく何か間違ったことをしていることを知った。これを行うためのより良い方法はありますか?
更新:
これまでの回答に基づいて、私は2つの可能性に傾いています。
- 純粋な仮想<=>関数を提供する<=>。これにより、<=>および<=>が正しい種類の<=>を返す実装を持つことができます。
- すべての<=>機能を<=>継承ツリーに吸収します。 <=>はメンテナンス上の理由で分割されました(<=>を別の場所に保管するため)。これは、小さなクラス(コード行が小さい)を増やすことと、大きなクラスを減らすこととのトレードオフです。
これらのソリューションの1つに対するコミュニティの強い選好はありますか?検討していない3番目のオプションはありますか?
解決
Carはエンジンポインターを保持していると想定しているため、ダウンキャストしていることに気付きます。
基本クラスからポインタを取り出し、純粋な仮想get_engine()関数に置き換えます。その後、FooCarとBarCarは正しいエンジンタイプへのポインターを保持できます。
(編集)
これが機能する理由:
仮想関数Car::get_engine()
は参照またはポインタを返すため、C ++では、派生クラスが異なる戻り値の型でこの関数を実装できるようになります。戻り値の型は、より派生した型であることによってのみ異なります。
これは共変の戻り型と呼ばれ、各Car
型が正しいEngine
。
他のヒント
追加したいことが1つあります。このデザインは、私が平行木と呼んでいるもののため、すでに私にとって悪臭があります。
基本的に(車とエンジンのように)並列クラス階層になった場合は、単にトラブルを求めているだけです。
エンジン(さらには車)にサブクラスが必要な場合、またはサブクラスがすべて同じベースクラスの異なるインスタンスであるかどうかを再考します。
次のようにエンジンタイプをテンプレート化することもできます
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
車をエンジンで構成できない理由がわかりません(BarCarに常にBarEngineが含まれる場合)。エンジンは車とかなり強い関係を持っています。 私は好む:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
FooCarがBarEngineを使用することは可能ですか?
そうでない場合は、AbstractFactoryを使用して、適切なエンジンで適切な自動車オブジェクトを作成することができます。
FooEngineにFooEngineを、BarCarにBarEngineを保存できます
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
でエンジンを設定する方法にはダウンキャストが必要になることです
Engine
がCar
とその子によってプライベートにのみ使用されるか、または他のオブジェクトでも使用するかどうかに依存すると思います。
virtual Engine* getEngine()
の機能がFooEngine
sに固有でない場合、基本クラスにポインターを保持する代わりにBarEngine
メソッドを使用します。
そのロジックが<=> sに固有の場合、共通の<=>データ/ロジックを別のオブジェクト(必ずしもポリモーフィックではない)に入れ、それぞれの<= >子クラス。
インターフェイスの継承よりも実装のリサイクルが必要な場合、オブジェクトの構成により柔軟性が向上することがよくあります。
MicrosoftのCOMはちょっと不器用ですが、斬新なコンセプトがあります-オブジェクトのインターフェイスへのポインターがある場合、 QueryInterface 関数。 Engineクラスを複数のインターフェイスに分割して、それぞれを独立して使用できるようにするという考え方です。
何かを見逃していないので、これはかなり簡単なはずです。
これを行う最良の方法は、インスタンス化される派生クラスで必要となる純粋な仮想関数をEngineで作成することです。
追加のクレジットソリューションは、おそらくIEngineと呼ばれるインターフェイスを持ち、代わりにCarに渡すことです。IEngineのすべての関数は純粋な仮想です。必要な機能の一部(つまり「共有」)を実装する「BaseEngine」を作成し、そこからリーフ機能を削除できます。
インターフェースは、エンジンのように「見た目」にしたい場合に便利ですが、おそらくそうではありません(つまり、模擬テストクラスなど)。
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)
{}
};