一些关于c++多重继承的基础知识

some basics about c++ multiple-inheritance

本文关键字:多重继承 基础知识 c++      更新时间:2023-10-16

在我的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() {}
};

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

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

此处出现编译错误。

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

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

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();
};

您正试图解决C++社区中臭名昭著的"死亡钻石问题"。Bjarne Stroustroup,C++的创建者,对这个问题给出了一个经典的说明。

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();
}

看看上面的类层次结构。正如您正确猜测的那样,编译器不知道在类Concrete的定义中应该采用哪种版本的Method1()和Method2()。由于Base1和Base2都有自己版本的Method1()和Method2(),编译器有两个选项来选择这些定义,它只是感到困惑,并开始抛出所有这些错误,到处都是"模棱两可"一词。

Bjarne Stroustroup为这个问题提出的解决方案是一个名为"虚拟继承"的诡计。实际上,当您从类"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。这样可以确保在定义时:混凝土等级:公共基层1、公共基层2{}

"Concrete"只获得"Base1"answers"Base2"都指向的"Base"的一个副本,从而解决了编译器的歧义。编译器是如何做到这一点的?每个具有虚拟方法的类都与称为虚拟函数表或vtable的东西相关联。vtable有一个指向该类中所有虚拟方法的指针。当加载类Base1和Base2时,这两个类的vtables会保存一个指向基类的指针。类似地,当加载Concrete时,Concrete的vtable也指向Base的同一个实例,从而有效地确保存在Base的单个实例。

在实例化Concrete时,这里有几点需要注意。您必须显式调用Base的构造函数,就像显式调用Base1和Base2的构造函数一样。类似的东西

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

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

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

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

此外,您可能希望公开这些函数。另一件非常重要的事情是始终在设计用于继承的类中声明虚拟析构函数,请参见例如:何时使用虚拟析构因子?。

类CCD_ 17实现接口CCD_。CCD_ 19使用方法CCD_ 21扩展接口CCD_。但是,当您继承Obj1(继承AbstractA)和AbstractB(也继承AbstractA)时,这会导致问题。因此,您可以"两次"继承AbstractA,除非您使用虚拟继承,否则这是不起作用的,请参阅:https://en.wikipedia.org/wiki/Virtual_inheritance.

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

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

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() */
};