C++多重继承的一些基础知识

some basics about c++ multiple-inheritance

在我的 C++ 中,我有如下内容

class AbstractA {
    void Foo() = 0;
    void Bar() = 0;
    void Woo() = 0;
};

class AbstractB : public AbstractA {
    void Doh() = 0;
};

class Obj1 : public AbstractA {
    void Foo() {}
    void Bar() {}
    void Woo() {}
};

现在我想定义一个新的 class Obj2,它是 Obj1 并且是(实现)AbstractB。基本上

class Obj2 : public Obj1, public AbstractB {
    void Doh();
};

此处出现编译错误

经过一些考虑,我怀疑我必须在 Obj2 中(重新)定义 Foo()Bar()Woo(),因为编译器不知道正确的路径按照以下步骤解决它们(基本上是从 Obj1AbstractB 传递过来的?)。我对这一点是否正确?

如果是这样,并且由于我解析 Foo()Bar()Woo() 的正确路径总是从 ObjA 传递,是否有任何语法可以避免父 class 调用每个方法?也就是说我能不能比

更简洁
class Obj2 : public Obj1, public AbstractB {
    void Doh();
    void Foo() { A::Foo() }
    void Bar() { A::Bar() }
    void Woo() { A::Woo() }
}

您可以使用virtual

class Obj2 : public Obj1, public virtual AbstractB {
    void Doh();
};

当使用 = 0 创建纯虚方法时,您还需要使用 virtual 关键字,例如

class AbstractA
{
   virtual void Foo() = 0;
   virtual void Bar() = 0;
   virtual void Woo() = 0;
};

此外,您可能还想制作这些功能public。 另一个非常重要的事情是 always 在 classes 中声明一个 virtual destructor designed to be inheritance, see example: When to use virtual destructors?.

class Obj1 实现了接口 AbstractA,因此是一个具体的 class。 AbstractB 使用方法 Doh 扩展接口 AbstractA。 但是,当您继承 Obj1(继承 AbstractA)和 AbstractB(也继承 AbstractA)时,这会导致问题。因此,您继承了 AbstractA 'twice',除非您使用 虚拟继承 ,否则它不起作用,请参阅:https://en.wikipedia.org/wiki/Virtual_inheritance.

更简单的方法是不让 AbstractB 继承自 AbstractA

因此,声明 classes 的一个好方法是:

class AbstractA
{
public:
    virtual ~AbstractA() {}
    virtual void Foo() = 0;
    virtual void Bar() = 0;
    virtual void Woo() = 0;
};

class AbstractB
{
public:
    virtual ~AbstractB() {}
    virtual void Doh() = 0;
};

/* This is now a concrete class that implements the 'interface' AbstractA. */
class Obj1 : public AbstractA
{
public:
    void Foo() {} /* Implements AbstractA::Foo() */
    void Bar() {} /* Implements AbstractA::Bar() */
    void Woo() {} /* Implements AbstractA::Woo() */
};

/* Inherits Obj1's implementations of AbstractA and implements AbstractB */
class Obj2 : public Obj1, public AbstractB
{
public:
    void Doh() {} /* Implements AbstractB::Woo() */
};

您正在尝试解决现在在 C++ 社区中臭名昭著的问题 "Diamond of Death Problem"。 C++ 的创造者 Bjarne Stroustroup 给出了这个问题的 classic 说明。

class Base {
  public:
  virtual void Method1() = 0;
  virtual void Method2() = 0;
};

class Base1 : public Base
{
  public:
  virtual void Method1();
  virtual void Method2();
};

class Base2 : public Base
{
  public:
    virtual void Method1();
    virtual void Method2();
}

class Concrete : public Base1, public Base2
{
  virtual void Method1();
  virtual void Method2();
}

查看上面的 class 层次结构。正如您猜对的那样,编译器不知道应该将哪个版本的 Method1() 和 Method2() 纳入 class 具体的定义中。由于 Base1 和 Base2 都有自己的 Method1() 和 Method2() 版本,编译器有 2 个选项来选择这些定义,它只是变得困惑并开始抛出所有这些错误,并在周围抛出单词 "ambiguous"地方。

Bjarne Stroustroup 针对此问题提出的解决方案是一个名为 "Virtual Inheritence" 的诡计。实际上,当您从 class "Base".

派生时,您要做的就是引入关键字 virtual
class Base1 : virtual public Base
{
  public:
  virtual void Method1();
  virtual void Method2();
};

class Base2 : virtual public Base
{
  public:
    virtual void Method1();
    virtual void Method2();
}

这告诉编译器,尽管在Base1 和Base2 中有多个Base 副本,但实际上它们应该指向相同版本的Base。 这确保当您定义: class 具体:public Base1,public Base2 { }

"Concrete" 只获得 "Base" 的一个副本,"Base1" 和 "Base2" 都指向它,从而解决了编译器的歧义。 编译器如何实现这一点?每个带有虚方法的 class 都与称为虚函数 table 或 vtable 的东西相关联。 vtable 有一个指向 class 中所有虚拟方法的指针。当加载 classes Base1 和 Base2 时,这两个 classes 的 vtables 持有一个指向 Base class 的指针。同理,加载Concrete时,Concrete的vtable也指向Base的同一个单例,有效保证了Base的单例。

这里实例化Concrete时需要注意的地方。您必须显式调用 Base 的构造函数,就像显式调用 Base1 和 Base2 的构造函数一样。像

Concrete() : Base(), Base1(), Base2()
{
}

另外,如果在Base1和Base2的构造函数中显式调用了Base()的构造函数,则实例化Concrete时会跳过,直接调用Base的构造函数。