【问题标题】:some basics about c++ multiple-inheritance关于 C++ 多重继承的一些基础知识
【发布时间】:2016-02-29 06:47:20
【问题描述】:

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

这里出现编译错误。

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

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

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

【问题讨论】:

  • 基本原则如果你不想做多重继承,你必须使用Virtaul显式其他明智的编译器会提示错误,因为编译器无法确定父级的路由多重继承也称为钻石问题

标签: c++ syntax-error multiple-inheritance virtual-functions


【解决方案1】:

您正在尝试解决现在在 C++ 社区中臭名昭著的可怕的“死亡钻石问题”。 C++ 的创造者 Bjarne Stroustroup 给出了这个问题的经典说明。

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

查看上面的类层次结构。正如您猜对的那样,编译器不知道应该将哪个版本的 Method1() 和 Method2() 纳入具体类的定义中。由于 Base1 和 Base2 都有自己的 Method1() 和 Method2() 版本,因此编译器有 2 个选项来选择这些定义,它只会感到困惑并开始抛出所有这些错误,并在各处抛出“模糊”一词。

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。 这可确保当您定义: 具体类:公共Base1,公共Base2 { }

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

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

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

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

【讨论】:

    【解决方案2】:

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

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

    此外,您可能希望公开这些功能。 另一个非常重要的事情是始终在设计用于继承的类中声明一个虚拟析构函数,例如:When to use virtual destructors?

    Obj1 类实现了AbstractA 接口,因此是一个具体类。 AbstractB 使用方法Doh 扩展了接口AbstractA。 但是,当您继承Obj1(继承AbstractA)和AbstractB(也继承AbstractA)时,这会导致问题。因此,您继承了AbstractA 'twice',除非您使用 虚拟继承,否则它不起作用,请参阅: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() */
    };
    

    【讨论】:

      【解决方案3】:

      您可以使用virtual

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

      【讨论】:

        最近更新 更多