假设我有以下类结构:
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
的成员函数而无需向下转换?
这就是为什么类结构按照现在的方式布局的原因:
Car
都有Engine
。此外,FooCar
只会使用FooEngine
。Engine
共享的数据和算法。Engine
了解其Car
。在编写此代码时,只要我输入dynamic_cast
,我就知道我可能做错了什么。有更好的方法吗?
更新:
根据目前给出的答案,我倾向于两种可能性:
Car
提供纯虚拟getEngine()
功能。这将允许FooCar
和BarCar
具有返回正确类型Engine
的实现。Engine
功能吸收到Car
继承树中。 Engine
由于维护原因而被分解(将Engine
内容保存在单独的位置)。这是在拥有更多小类(代码行数较少)与较少大类之间的权衡。社区对这些解决方案之一的偏好是否很强?我还没考虑过第三种选择吗?
答案 0 :(得分:24)
我假设Car拥有一个引擎指针,这就是为什么你会发现自己的低迷。
将指针从基类中取出,并将其替换为纯虚拟get_engine()函数。然后你的FooCar和BarCar可以保存指向正确引擎类型的指针。
<强>(编辑)强>
为什么会这样:
由于虚函数Car::get_engine()
将返回引用或指针,因此C ++将允许派生类使用不同的返回类型来实现此函数,如只要返回类型因更多派生类型而不同。
这称为covariant return types,并允许每个Car
类型返回正确的Engine
。
答案 1 :(得分:10)
我想补充一点:这个设计对我来说已经闻起来很糟糕,因为我称之为并行树。
基本上如果你最终得到了并行类层次结构(就像你使用Car和Engine那样),那么你只是在寻找麻烦。
如果Engine(甚至是Car)需要有子类,或者那些只是相同的基类的不同实例,我会重新考虑。
答案 2 :(得分:7)
您还可以按如下方式对引擎类型进行模板化
template<class EngineType>
class Car
{
protected:
EngineType* getEngine() {return pEngine;}
private:
EngineType* pEngine;
};
class FooCar : public Car<FooEngine>
class BarCar : public Car<BarEngine>
答案 3 :(得分:4)
我不明白为什么汽车不能由引擎组成(如果BarCar将始终包含BarEngine)。发动机与汽车有很强的关系。 我更喜欢:
class BarCar:public Car
{
//.....
private:
BarEngine engine;
}
答案 4 :(得分:2)
FooCar是否可以使用BarEngine?
如果没有,您可能希望使用AbstractFactory创建正确的汽车对象,并使用正确的引擎。
答案 5 :(得分:2)
您可以将FooEngine存储在BarCar中的FooCar,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
中设置引擎的方法需要向下转换
答案 6 :(得分:1)
我认为这取决于Engine
是否仅由Car
及其子级私下使用,或者您是否也想在其他对象中使用它。
如果Engine
功能不是Car
特有的,我会使用virtual Engine* getEngine()
方法,而不是在基类中保留指针。
如果它的逻辑特定于Car
s,我宁愿将公共Engine
数据/逻辑放在一个单独的对象中(不一定是多态的)并保持FooEngine
和{{1在各自的BarEngine
子类中实现。
当实现回收比接口继承更需要时,对象组合通常提供更大的灵活性。
答案 7 :(得分:1)
微软的COM有点笨拙,但确实有一个新概念 - 如果你有一个指向对象接口的指针,你可以查询它是否支持使用QueryInterface函数的任何其他接口。我们的想法是将您的Engine类分解为多个接口,以便每个接口可以独立使用。
答案 8 :(得分:0)
允许我没有错过任何东西,这应该是相当微不足道的。
执行此操作的最佳方法是在Engine中创建纯虚函数,然后在要实例化的派生类中需要这些函数。
额外的信用解决方案可能是拥有一个名为IEngine的界面,您可以将其传递给您的Car,而IEngine中的每个功能都是纯虚拟的。您可以使用“BaseEngine”来实现您想要的某些功能(即“共享”),然后再使用它们。
界面很好,如果你想要“看起来”像引擎一样,但可能不是(即模拟测试类等)。
答案 9 :(得分: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)
{}
};