虚拟继承对构造函数的影响

问题描述 投票:1回答:3

我已经开始学习虚拟继承(以及如何解决从具有相同父代的两个父类派生一个类的问题)。为了更好地了解其背后的机制,我举了一个下面的例子:

class A {
public: 
    A(string text = "Constructor A") { cout << text << endl; }
};

class B:  public A {
public:
    B(): A("A called from B") { cout << "Constructor B" << endl; }
};

class C : virtual public A {
public:
    C() : A("A called from C") { cout << "Constructor C" << endl; }
};

class D :  public B,  public C {
public: 
    D() { cout << "Constructor D" << endl; }
};

我有class A,从class B派生的A,实际上从class C派生的A和从class DB派生的C。在主要我只是形成class D的对象:D d;,我得到以下输出

Constructor A
A called from B
Constructor B
Constructor C
Constructor D

让我烦恼的是,为什么会有“构造函数A”表示没有被class BC调用。并且为什么在“ Constructor C”之前没有“ A from C”。对于后者,我知道它实际上与class C的虚拟派生有关,因此我猜它不会再次调用Constructor A,因为来自class A的对象已经形成(实际上是两次)。

编辑:

总的来说,我只使一个对象类型为D。

int main() {
  D d;
}
c++ inheritance multiple-inheritance virtual-inheritance
3个回答
4
投票

首先,由于B是非虚拟地从A派生的,所以D最终有两个A子对象(好像您根本没有使用virtual继承一样。

[非虚拟AB()构造(因此为"A called from B"),并且在构造时虚拟将打印"Constructor A"

这是因为虚拟(可能是间接)基的构造函数总是由派生最多的类(D)的构造函数调用,而不是由任何中间基类(C)的构造函数调用。

这意味着: A("A called from C")中的C()被忽略(因为您没有构造独立的C)。并且,由于D()在其成员初始化器列表中未提及A,因此虚拟A的构造没有任何参数(就好像由: A()一样)。


另外,值得一提的是,所有虚拟基础都是constructed before非虚拟基础。

因此您的代码保证可以在Constructor A之前打印A called from B


3
投票

问题

您的代码忘记了什么,并且A对象中仍然有两个D对象。您可以通过将公共int test成员添加到A并尝试以下代码来验证此声明。您将获得两个不同的地址:

D d;
cout << &d.B::test <<endl;   // the non virtual A subobject of B
cout << &d.C::test <<endl;   // the virtual A subobject of C

Online demo

解决方案

所有类虚拟共享A并直接从A继承必须声明虚拟继承。因此,您需要更正B类:

class B:   virtual public A {...}  // you forgot the virtual here 

上面的代码段可以按预期工作,您甚至可以寻址d.test而不会出现任何歧义错误。 Online demo

编辑:A的精致构造>

C ++规则要求每个对象都带有虚拟A子对象,以为A提供构造函数。对于您的情况,D应该提供虚拟A的结构。

由于没有显式构造函数,因此D将查找默认构造函数A。它找到一个,因为您为A构造函数提供了默认参数。这具有误导性,因为它实际上告诉您“构造函数”是D在使用它。

如果删除此默认参数,则代码将不再编译。然后,您需要类似以下内容来正确构建A

class A {
public: 
    int test; 
    A(string text) { cout << "A is constructed: "<<text << endl; }
};

class D :  public B,  public C {
public: 
    D() : A("Mandatory, if there is no default consructor") { cout << "Constructor D" << endl; }
};

Online demo

为什么这样的C ++构造规则?

因为当您具有虚拟继承时,没有理由将AB构造画在C的构造上。也不相反。另一方面,BC都定义了如何构造其A子对象。为了解决选择正确结构时的歧义,决定了该规则。如果虚拟类没有默认构造函数,可能会很痛苦。

2
投票

[当类型具有虚拟基数时,虚拟基数是从最派生类型的构造函数构造的。因此,从D的构造函数中调用了“构造函数A”。

© www.soinside.com 2019 - 2024. All rights reserved.