【问题标题】:Multiple inheritance with two interfaces, one which derives from first, and a derived class implementing the first interface具有两个接口的多重继承,一个从第一个接口派生,一个派生类实现第一个接口
【发布时间】:2021-04-07 13:00:13
【问题描述】:

很抱歉描述不佳。问题来了:

class PureVirtualBase {
public:
    virtual ~PureVirtualBase() {}
    virtual int IntFn() = 0;
};

class PureVirtualDerivedBase : public PureVirtualBase {
public:
    virtual ~PureVirtualDerivedBase() {}
    virtual int OtherIntFn() = 0;
};

class Foo : public PureVirtualBase {
public:
    virtual int IntFn() { return intVal; }
protected:
    int intVal;
};

class Bar : public Foo, public PureVirtualDerivedBase {
public
    virtual int OtherIntFn() { return 123; }
};

由于“'Bar”而失败:由于以下成员而无法实例化抽象类:'int PureVirtualBase::IntFn(void) is abstract"

我不确定如何纠正这个问题。我会认为 Bar 很好,因为它继承了 Foo 的 IntFn 实现(并根据 PureVirtualDerivedBase 的要求添加了 OtherIntFn)。我尝试过将继承设为虚拟(public virtual Foo、public virtual PureVirtualBase),但没有成功。

有什么想法吗?提前致谢。

【问题讨论】:

  • 从抽象类派生时,如果您不实现纯虚方法,您的类也会变成抽象类,因此您无法实例化该类型的对象

标签: c++ multiple-inheritance pure-virtual virtual-inheritance


【解决方案1】:

我对您的类结构设计的主要关注是,在您提供的示例中,PureVirtualDerivedBasePureVirtualBase 继承似乎是多余的。通过在另一个抽象类中派生抽象类来扩展一个抽象类可能会使您的继承树变得混乱,在最坏的情况下,您最终可能不得不为您在基类中声明的抽象方法创建重复的实现。

在这种情况下,您必须在Bar 中重新实现IntFn(),因为继承PureVirtualDerivedBase 明确要求您实现与其关联的所有虚方法,无论它们是否已在Foo 中实现。

我的建议是从PureVirtualDerivedBase 中删除PureVirtualBase 继承,并且只在实际实现抽象方法的类中继承抽象类。为清楚起见,最好避免使接口依赖于其他接口(如果可以避免)。当您希望为派生类实现不同的功能时,请尝试使接口尽可能自包含并从多个接口继承。

【讨论】:

    【解决方案2】:

    这是the diamond problem 的细微变化。基本上在 c++ 中,每个继承路径都是单独遵循的,这意味着类Bar 有一个Foo 的副本和一个PureVirtualDerivedBase 的副本。这解释了您面临的问题。

    但是,我看不出从PureVirtualDerivedBase 显式派生Bar 的意义,因为Foo 已经有了它。

    【讨论】:

      【解决方案3】:

      错误在于如何应用虚拟继承。

      具体来说,这个问题可以通过使用虚拟继承来解决,只是不是以最初描述的方式(即不是“public virtual Foo,public virtual PureVirtualBase”)。

      FooPureVirtualDerivedBase 都需要虚拟继承 PureVirtualBase

      class PureVirtualBase {
      public:
          virtual ~PureVirtualBase() {}
          virtual int IntFn() = 0;
      };
      
      class PureVirtualDerivedBase : public virtual PureVirtualBase {
      public:
          virtual ~PureVirtualDerivedBase() {}
          virtual int OtherIntFn() = 0;
      };
      
      class Foo : public virtual PureVirtualBase {
      public:
          virtual int IntFn() { return intVal; }
      protected:
          int intVal;
      };
      
      class Bar : public Foo, public PureVirtualDerivedBase {
      public
          virtual int OtherIntFn() { return 123; }
      };
      

      这样,Bar 可以将OtherIntFn 的实现添加到Foo,而不会丢失FooIntFn 实现。

      Raviteja 是正确的,这是钻石问题的变体。但是,我们需要Bar 继承自PureVirtualDerivedBase 才能添加OtherIntFn 的附加功能。

      我同意 Revelnaut 的观点,尽量避免这种事情是好的,但在某些情况下这样做会很尴尬/不方便,尤其是当一个界面显然是另一个界面的子集时。

      这是一个更具体的例子:

      class IList {
      public:
          virtual ~IList() {}
          virtual int GetLength() = 0;
      };
      
      class IDynamicList : public virtual IList {
          virtual ~IDynamicList() {}
          virtual int GetCapacity() = 0;
      };
      
      class ListOfFoo : public virtual IList {
      public:
          virtual ~ListOfFoo() {}
          virtual int GetLength() { return length; }
          void UsefulFooFunction();
      
      protected:
          int length;
      };
      
      class DynamicFooList : public ListOfFoo, public IDynamicList {
      public:
          virtual ~DynamicFooList() {}
          virtual int GetCapacity() { return capacity; }
          void FooSpecificFnThatGrowsList();
      
      protected:
          int capacity;
      };
      

      在这种情况下,IList 的功能是 IDynamicList 功能的子集。

      您可以分离接口,但指向IDynamicList 的指针将无法告诉您列表的长度;它只能告诉你容量。在这种情况下,当IDynamicList 显然已经是一个列表时,传递/检索一个额外的指针来获取IList 提供的功能会很麻烦。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-10-22
        • 2023-04-05
        • 2020-12-09
        • 1970-01-01
        • 2015-04-19
        • 2021-09-12
        • 2019-08-04
        • 2010-09-22
        相关资源
        最近更新 更多