18

假设我有以下类结构:

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

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

让我们也给它Car一个句柄Engine。AFooCar将由 a 创建,FooEngine*aBarCar将由 a 创建BarEngine*。有没有办法安排事情,以便FooCar对象可以调用成员函数而FooEngine无需向下转换?

这就是为什么类结构按现在的方式布局的原因:

  1. 所有Cars 都有一个Engine. 此外, aFooCar只会使用 a FooEngine
  2. 所有Engines 共享的数据和算法我宁愿不复制和粘贴。
  3. 我可能想编写一个需要Engine知道它的函数Car

在编写这段代码时,我一打字dynamic_cast就知道我可能做错了什么。有一个更好的方法吗?

更新:

根据目前给出的答案,我倾向于两种可能性:

  1. 已经Car提供了一个纯虚getEngine()函数。这将允许FooCarBarCar拥有返回正确类型的Engine.
  2. Engine将所有功能吸收到Car继承树中。 Engine出于维护原因(将这些Engine东西保存在一个单独的地方)。这是拥有更多小类(小代码行)与拥有更少大类之间的权衡。

社区是否对其中一种解决方案有强烈的偏好?有没有我没有考虑过的第三种选择?

4

10 回答 10

24

我假设 Car 拥有一个 Engine 指针,这就是你发现自己垂头丧气的原因。

将指针从基类中取出,并将其替换为纯虚拟 get_engine() 函数。然后您的 FooCar 和 BarCar 可以保存指向正确引擎类型的指针。

(编辑)

为什么这样有效:

由于虚函数Car::get_engine()将返回一个引用或一个指针,C++ 将允许派生类以不同的返回类型来实现这个函数,只要返回类型的区别仅在于是一个更派生的类型。

这称为协变返回类型,并且将允许每种Car类型返回正确的Engine

于 2009-01-19T22:58:32.613 回答
10

我只想补充一件事:这个设计对我来说已经很糟糕了,因为我称之为平行树

基本上,如果您最终得到并行的类层次结构(就像 Car 和 Engine 一样),那么您只是在自找麻烦。

我会重新考虑引擎(甚至汽车)是否需要有子类,或者这些子类都只是相同各自基类的不同实例。

于 2009-01-19T22:57:28.247 回答
7

您还可以将引擎类型模板化如下

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

FooCar 可以使用 BarEngine 吗?

如果没有,您可能希望使用 AbstractFactory 来创建正确的汽车对象和正确的引擎。

于 2009-01-19T22:49:55.767 回答
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 回答
1

我认为这取决于是否EngineCar由其子对象私人使用,或者您是否还想在其他对象中使用它。

如果Engine功能不是特定于Cars 的,我会使用virtual Engine* getEngine()方法而不是在基类中保留指针。

如果它的逻辑是特定于Cars 的,我更愿意将公共Engine数据/逻辑放在一个单独的对象中(不一定是多态的),并在它们各自的子类中保留FooEngine和实现。BarEngineCar

当实现循环比接口继承更需要时,对象组合通常提供更大的灵活性。

于 2009-01-20T03:35:54.940 回答
1

微软的 COM 有点笨拙,但它确实有一个新颖的概念——如果你有一个指向对象接口的指针,你可以使用QueryInterface函数查询它是否支持任何其他接口。这个想法是将您的 Engine 类分解为多个接口,以便每个接口都可以独立使用。

于 2009-01-20T04:24:23.267 回答
0

允许我没有错过任何东西,这应该是微不足道的。

最好的方法是在 Engine 中创建纯虚函数,然后在要实例化的派生类中需要这些函数。

一个额外的解决方案可能是拥有一个名为 IEngine 的接口,您可以将其传递给您的 Car,并且 IEngine 中的每个功能都是纯虚拟的。你可以有一个'BaseEngine'来实现你想要的一些功能(即'shared'),然后有叶子的。

如果您想要“看起来”像引擎的东西,但可能不是(即模拟测试类等),界面会很好。

于 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 回答