【问题标题】:Interface Inheritance in C++C++中的接口继承
【发布时间】:2023-03-04 02:16:01
【问题描述】:

我有以下类结构:

class InterfaceA
{ 
   virtual void methodA =0;
}

class ClassA : public InterfaceA
{
   void methodA();
}

class InterfaceB : public InterfaceA
{
   virtual void methodB =0;
}

class ClassAB : public ClassA, public InterfaceB
{ 
   void methodB(); 
}

现在以下代码无法编译:

int main()
{
    InterfaceB* test = new ClassAB();
    test->methodA();
}

编译器说methodA() 方法是虚拟的并且没有实现。我认为它是在ClassA 中实现的(它实现了InterfaceA)。 有谁知道我的错在哪里?

【问题讨论】:

    标签: c++ inheritance interface


    【解决方案1】:

    那是因为您有两份InterfaceA。请参阅此以获得更大的解释:https://isocpp.org/wiki/faq/multiple-inheritance(您的情况类似于“可怕的钻石”)。

    从InterfaceA继承ClassA时需要添加关键字virtual。从 InterfaceA 继承 InterfaceB 时,还需要添加virtual

    【讨论】:

    • 感谢劳拉的澄清。
    • 我认为一旦一个函数被声明virtual在整个类层次结构中总是虚拟的,无论派生类在定义它时是否实际使用virtual
    【解决方案2】:

    Laura 建议的虚拟继承当然是解决问题的方法。但它最终不会只有一个InterfaceA。例如,它也有“副作用”。见https://isocpp.org/wiki/faq/multiple-inheritance#mi-delegate-to-sister。但如果习惯了,它可能会派上用场。

    如果你不想要副作用,你可以使用模板:

    struct InterfaceA
    { 
      virtual void methodA() = 0;
    };
    
    template<class IA>
    struct ClassA : public IA //IA is expected to extend InterfaceA
    {
      void methodA() { 5+1;}
    };
    
    struct InterfaceB : public InterfaceA
    {
      virtual void methodB() = 0;
    };
    
    struct ClassAB 
      : public ClassA<InterfaceB>
    { 
      void methodB() {}
    };
    
    int main()
    {
      InterfaceB* test = new ClassAB();
      test->methodA();
    }
    

    所以,我们只有一个父类。

    但是当有多个“共享”类时,它看起来更难看(InterfaceA 是“共享的”,因为它位于“可怕的钻石”之上,请参见此处 https://isocpp.org/wiki/faq/multiple-inheritance 由 Laura 发布)。查看示例(如果 ClassA 也实现了 interfaceC,将会是什么):

    struct InterfaceC
    {
      virtual void methodC() = 0;
    };
    
    struct InterfaceD : public InterfaceC
    {
      virtual void methodD() = 0;
    };
    
    template<class IA, class IC>
    struct ClassA
      : public IA //IA is expected to extend InterfaceA
      , public IC //IC is expected to extend InterfaceC
    {
      void methodA() { 5+1;}
      void methodC() { 1+2; }
    };
    
    struct InterfaceB : public InterfaceA
    {
      virtual void methodB() = 0;
    };
    
    struct ClassAB
      : public ClassA<InterfaceB, InterfaceC> //we had to modify existing ClassAB!
    { 
      void methodB() {}
    };
    
    struct ClassBD //new class, which needs ClassA to implement InterfaceD partially
      : public ClassA<InterfaceB, InterfaceD>
    {
      void methodB() {}
      void methodD() {}
    };
    

    不好的是,您需要修改现有的 ClassAB。但是你可以写:

    template<class IA, class IC = interfaceC>
    struct ClassA
    

    那么ClassAB保持不变:

    struct ClassAB 
          : public ClassA<InterfaceB>
    

    而且你有模板参数 IC 的默认实现。

    使用哪种方式由您决定。我更喜欢模板,因为它很容易理解。很难养成习惯, B::incrementAndPrint() 和 C::incrementAndPrint() 将打印不同的值(不是您的示例),请参见:

    class A
    {
    public:
      void incrementAndPrint() { cout<<"A have "<<n<<endl; ++n; }
    
      A() : n(0) {}
    private:
      int n;
    };
    
    class B
      : public virtual A
    {};
    
    class C
      : public virtual A
    {};
    
    class D
      : public B
      : public C
    {
    public:
      void printContents()
      {
        B::incrementAndPrint();
        C::incrementAndPrint();
      }
    };
    
    int main()
    {
      D d;
      d.printContents();
    }
    

    还有输出:

    A have 0
    A have 1
    

    【讨论】:

      【解决方案3】:

      存在这个问题是因为 C++ 没有真正的接口,只有具有多重继承的纯虚拟类。编译器不知道在哪里可以找到methodA() 的实现,因为它是由ClassAB 的不同基类实现的。您可以通过在 ClassAB() 中实现 methodA() 来调用基本实现来解决此问题:

      class ClassAB : public ClassA, public InterfaceB
      { 
          void methodA()
          {
              ClassA::methodA();
          }
      
          void methodB(); 
      }
      

      【讨论】:

      • 如果 C++ 的规则与 Java 的规则相似,编译器仍然不会知道它,因为指针被声明为 InterfaceB,它没有 methodA
      • mmyers:这对 C++ 和 Java 来说都是错误的。 InterfaceB 继承自 InterfaceA,其中定义了 methodA。
      • InterfaceB 继承自 interfaceA 所以它应该有 methodA
      • 上述解决方案是我解决问题的第一种方法。它有效,但处理起来非常糟糕。 (如果接口 A 发生变化,我也必须在每个继承的类中进行更改)
      【解决方案4】:

      你这里有一颗可怕的钻石。 InterfaceB 和 ClassA 必须从 InterfaceA 虚拟继承 否则,您的 ClassAB 有两个 MethodA 副本,其中一个仍然是纯虚拟的。您应该无法实例化此类。即使你是 - 编译器也无法决定调用哪个 MethodA。

      【讨论】:

        猜你喜欢
        • 2011-04-21
        • 1970-01-01
        • 2011-03-24
        • 1970-01-01
        • 2023-03-14
        • 2013-11-25
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多