【问题标题】:A class inheritance C++类继承 C++
【发布时间】:2016-10-21 16:53:14
【问题描述】:

我有一个关于类继承的问题。

我认为下面的代码可以解释我的问题:

struct A {
   int x;
};

struct B: A {
};

struct C: A {
};

struct D: B, C {
    D() : x(1) {}
};

int main() {
    D d;
}

代码无法编译。

问题是:如何创建结构 D 的实例?谢谢!

【问题讨论】:

  • 不要发布代码图片,而是将相关代码本身作为(格式正确的)文本发布。
  • 请想办法将所有必要的信息放入您的问题中,否则您的问题对其他读者毫无用处。
  • 删除了我的回答 πάντα ῥεῖ
  • @PavanChandaka 勇敢的家伙 :)
  • 您不能在子类(或本例中的结构)的构造函数初始化列表中初始化父成员。为 x 赋值或更好地为A 创建一个接受int 并使用虚拟继承的构造函数

标签: c++ inheritance compiler-errors multiple-inheritance


【解决方案1】:

您正在尝试做的事情有两个问题。

  1. D 没有名为x 的成员,它有两个 名为x 的成员。具体来说,D::B::xD::C::x,由于多重继承和称为diamond problem 的某个问题。由于BC 都是从A 派生的,它们各自在其定义中都包含A 的整体,因此每个都包含自己的A::x。因此,当D 派生自BC 时,它本身包含一个完整的B 和一个完整的C,包括它们的As。它看起来像这样(使用 MSVC 生成,使用编译器开关 -d1reportSingleClassLayout):

    class D size(8):
        +---
        | +--- (base class B)
        | | +--- (base class A)
     0  | | | x
        | | +---
        | +---
        | +--- (base class C)
        | | +--- (base class A)
     4  | | | x
        | | +---
        | +---
        +---
    

    您应该改为修改 BC 以虚拟继承 A,如下:

    struct B: virtual A {
    };
    
    struct C: virtual A {
    };
    

    这将导致BC 不包含它们的基础A,而是让它跟随它们到任何地方:

    class B size(8):
        +---
     0  | {vbptr}
        +---
        +--- (virtual base A)
     4  | x
        +---
    
    class C size(8):
        +---
     0  | {vbptr}
        +---
        +--- (virtual base A)
     4  | x
        +---
    

    这反过来又允许D 拿走一个A,将A 贴在后面,然后告诉他们俩这是他们的 A,解决了钻石问题并允许他们共享同一个实例。

    class D size(12):
        +---
        | +--- (base class B)
     0  | | {vbptr}
        | +---
        | +--- (base class C)
     4  | | {vbptr}
        | +---
        +---
        +--- (virtual base A)
     8  | x
        +---
    
  2. 一个类只能在member initialiser list中初始化自己的成员;不允许初始化其基类的成员。这是因为 C++ 中的对象是分步构造的:

    • 首先,构造继承层次结构中任何地方遇到的任何虚拟基。派生最多的类被认​​为负责它们的构造。 [如果最派生类在其初始化列表中提及其任何虚拟基,则该虚拟基将被传递指定的参数。如果任何直接基类在其初始化列表中提及任何虚拟基类,它将被忽略。]
    • 然后,构建对象的直接基类(如果有)。对象构造规则以递归方式应用,这意味着将首先构造最小派生类(即位于继承层次结构最底部的类)。如果直接基类具有虚拟基类,则此时不会构造该虚拟基类。 [如果派生类在其初始化列表中提及其任何直接基类,则该基类将被传递指定的参数。]
    • 最后,派生类是围绕它的直接基类构建的,然后是它的虚拟基类。此时构造非静态数据成员;如果在初始化列表中,它们将使用指定的参数构造,否则默认构造。

    因此,D 应该将参数传递给A 的构造函数,以便他们可以初始化字段。

    struct A {
        int x;
    
        // If A() has no parameter specified, it sets x to 4.
        A(int x_ = 4) : x(x_) {}
    };
    
    struct B: virtual A {
        // If B constructs A, it tells it to set x to 3 unless otherwise specified.
        B(int x_ = 3) : A(x_) {}
    };
    
    struct C: virtual A {
        // If C constructs A, it tells it to set x to 2 unless otherwise specified.
        C(int x_ = 2) : A(x_) {}
    };
    
    struct D: B, C {
        // This will actually set D.x to 4; since D constructs A, neither B() nor C() will
        //  call A().  D() will call A() without specifying a parameter.
        // D() : B(1), C(1) {}
    
        // This, however, gets the job done.  Tells A to set x to 1.
        D() : A(1) {}
    };
    

    那么,当你构造d...

    int main() {
        D d;
    }
    

    其成员 d.x 现在将按预期设置为 1

    请注意,如果您出于某种原因确实希望D 包含两个A 实例,从而避免使用虚拟继承,则应使用第一个版本的而是构造函数。这样,当d被构造时,d.B::xd.C::x都会被设置为1。


感谢curiousguy 指出虚拟碱基实际上是在直接碱基之前构建的,而不是我认为的在它们之后。

【讨论】:

  • 子对象总是在对象之前构造。
  • 是的,我在回答的第 2 节中提到了这一点。首先构造一个对象的基类,然后是它的虚拟基类,最后是它,递归地应用该顺序。
  • 所以你同意基类ctor总是在其派生类ctor之前被调用?
  • 是的。 “首先,构造对象的直接基类(如果有的话)。递归应用对象构造规则,这意味着最小派生类(即位于继承层次结构最底部的类)将是先建好了。”
  • 所以你同意虚拟基础子对象的构造总是在派生子对象的任何构造之前完成,这与你的描述相反?
猜你喜欢
  • 2011-01-19
  • 1970-01-01
  • 2015-12-09
  • 2018-11-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-05
相关资源
最近更新 更多