【问题标题】:How can I avoid dynamic_cast in my C++ code?如何在我的 C++ 代码中避免 dynamic_cast?
【发布时间】:2010-10-02 08:24:33
【问题描述】:

假设我有以下类结构:

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

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

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

这就是为什么类结构是现在这样布置的原因:

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

当我在编写这段代码时输入dynamic_cast,我就知道我可能做错了什么。有没有更好的方法来做到这一点?

更新:

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

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

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

【问题讨论】:

  • 也许你应该让问题的标题更具体一点?比如“如何避免使用 dynamic_cast”。顺便说一句,这是一个很好的问题。

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


【解决方案1】:

FooCar 可以使用 BarEngine 吗?

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

【讨论】:

  • 如果我理解 OP 的问题,那将无济于事,因为继承的 Engine 类型的“引擎”成员变量让他头疼……他希望它是协变的,我不这样做'认为在 C++ 中是不允许的,所以我避免发布非答案...
【解决方案2】:

如果我没有错过什么,这应该是相当微不足道的。

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

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

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

【讨论】:

    【解决方案3】:

    有没有办法安排事情,以便 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)
      {}
    };
    

    【讨论】:

    • 我可以看到这种设计会导致各种问题。如果 Car 定义了一个 void setEngine(Engine* engine) 方法怎么办?外部客户端可以在 FooCar 上调用它,现在您的对象的完整性已经消失。
    • 如果 Car 定义了一个“void setEngine(Engine* engine)”方法,那么这个方法需要被保护,或者抽象。或者,在 Car 中使用“Engine* m_engine”的替代方法是在 Car 中使用私有抽象“Engine* get_engine()”方法。
    • 在我的情况下不会有 setEngine 功能,但我同意这可能会导致维护问题。 Car 的子类对于同一个 Engine 有两个句柄(具有不同的类型!)会让人感到困惑。
    【解决方案4】:

    我只想补充一点:由于我称之为平行树,这种设计对我来说已经很糟糕了。

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

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

    【讨论】:

    • 你能提供一个具体的例子(给定我上面的类结构)如何解决“平行树”问题吗?
    • 我也会对“合适的”解决方案感兴趣。我也总是不喜欢那些平行树,但无法真正解释为什么或者——更重要的是——想出更好的解决方案。
    【解决方案5】:

    我假设 Car 拥有一个 Engine 指针,这就是为什么你会发现自己垂头丧气。

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

    (编辑)

    为什么会这样:

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

    这称为covariant return types,将允许每个Car 类型返回正确的Engine

    【讨论】:

    • 并且由于虚函数允许协变返回类型,您可以将返回值设为适当的 FooEngine 或 BarEngine 类型! +1 我忽略了这个简单的解决方案。
    • 这是要走的路。 @Kristo,试试看。
    • 这如何推迟向下转换问题?
    • Kristo:查一下协变返回类型,你就明白了。关键是不要指定在超类中实际存储引擎的成员变量——只使用这个抽象访问器,它在子类中被覆盖以使用适当类型的成员变量。
    • @rmeador:我理解你的回答。我应该用“@cletus”标记我的评论。
    【解决方案6】:

    您可以将 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设置引擎的方法需要向下转换。

    【讨论】:

    • 我支持你的回答。但我问:为什么要“设置” FooCar 的 FooEngine?汽车构造函数还应该构建汽车自己的引擎: FooCar() {engine = new FooEngine(); } 引擎* getEngine() { 返回引擎; }
    • 这完全取决于OP想要做什么。真车的发动机是可以更换的,这甚至是有法律程序的。因此,如果他想对此进行建模,则必须执行某种 setEngine() 方法。
    • 在这种情况下永远不会改变汽车的引擎。
    【解决方案7】:

    我不明白为什么汽车不能由引擎组成(如果 BarCar 将始终包含 BarEngine)。发动机与汽车的关系非常密切。 我更喜欢:

    class BarCar:public Car
    {
       //.....
       private:
         BarEngine engine;
    }
    

    【讨论】:

    • 我不认为这对我有用,因为 Car 基类需要用它的 Engine 来做事。
    • 当然.. 是什么阻止了 Car 的私有方法调用引擎的公共方法?除非你试图进一步抽象它......即 Car 类由 Engine 指针组成。
    【解决方案8】:

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

    template<class EngineType>
    class Car
    {
        protected:
            EngineType* getEngine() {return pEngine;}
        private:
            EngineType* pEngine;
    };
    
    class FooCar : public Car<FooEngine>
    
    class BarCar : public Car<BarEngine>
    

    【讨论】:

    • 如果我理解正确,这基本上等同于建议纯虚拟 getEngine 函数的答案。这是编译时与运行时的多态性,不是吗?
    • 这种方法的一个限制是您不能再将FooCar 作为Car &amp;Car * 传递-如果您想用它做任何Car-generic,您有将其传递给模板函数/方法/类型。
    • 对——这是编译时多态性,而不是运行时。
    • @JohnMcG +1 for polymotorphism :)
    【解决方案9】:

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

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

    如果它的逻辑特定于Cars,我宁愿将通用的Engine 数据/逻辑放在一个单独的对象中(不一定是多态的),并将FooEngineBarEngine 实现保留在它们各自的@987654330 中@子类。

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

    【讨论】:

      【解决方案10】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-04
        • 1970-01-01
        相关资源
        最近更新 更多