如何在C ++代码中避免使用dynamic_cast?

时间:2009-01-19 22:47:09

标签: c++ oop inheritance dynamic-cast car-analogy

假设我有以下类结构:

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。此外,FooCar只会使用FooEngine
  2. 我不想复制和粘贴所有Engine共享的数据和算法。
  3. 我可能想要编写一个函数,要求Engine了解其Car
  4. 在编写此代码时,只要我输入dynamic_cast,我就知道我可能做错了什么。有更好的方法吗?

    更新:

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

    1. Car提供纯虚拟getEngine()功能。这将允许FooCarBarCar具有返回正确类型Engine的实现。
    2. 将所有Engine功能吸收到Car继承树中。 Engine由于维护原因而被分解(将Engine内容保存在单独的位置)。这是在拥有更多小类(代码行数较少)与较少大类之间的权衡。
    3. 社区对这些解决方案之一的偏好是否很强?我还没考虑过第三种选择吗?

10 个答案:

答案 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)
  {}
};