2017-04-24 33 views
12

考虑:为什么这个钻石类的继承输出不是我所期望的?

#include <iostream> 

using namespace std; 

class A {// base class 
private: 
    int data; 
public: 
    A(int data = 0) 
    { 
     this->data = data; 
    } 
    void show() 
    { 
     cout << data << endl; 
     return; 
    } 
}; 

class B : virtual public A { 
public: 
    B(int data = 0) : 
     A(data) { 
    } 
}; 

class C : virtual public A { 
public: 
    C(int data = 0) : 
     A(data) { 
    } 
}; 

class D : public B, public C { 
public: 
    D(int dataB = 0, int dataC = 0) : 
     B(dataB), 
     C(dataC) { 
    } 
}; 

int main() { 
    D d(1, 2); 
    d.B::show(); 
    d.C::show(); 
    return 0; 
} 

上面的代码是钻石类的继承图。基类是A.我使用虚拟继承来避免钻石问题。但为什么这个程序的输出是,0,0,而不是1,2正如我所料?

B的构造函数通过data=1,并且在其初始化程序列表中它调用AdataC的构造函数类似于data=2及其初始化列表,它调用Adata

然后我们要求BC子对象show它们的值。我们得到00不是12正如我所料。

+1

你可以非常容易地拥有1,2个 - 只是让这个继承不是虚拟的:)然后你将拥有两个A的拷贝 –

+0

无论如何'this->'很奇怪。在现代C++中,你可以编写'A(int data = 0):data(data){','构造函数初始化列表',它更干净,并且允许你拥有常量成员。 –

+1

这不是问题,但是你真的需要额外的东西,'std :: endl'吗? ''\ n''结束一行。 –

回答

22

,当你用虚拟继承这个方案,它是由在层次结构中最派生类(在这种情况下D)来调用公共基础(A1,2的构造。

由于A的构造函数有一个默认参数data = 0,所以它可以用作默认构造函数。这就是发生了什么事情,因为你从D的成员初始化列表中删除了它,所以常见的A子对象被默认构造。

如果删除默认值data,你会得到强调一个不错的编译器错误:

A(int data) 
{ 
    this->data = data; 
} 

On g++ it results with

main.cpp: In constructor 'D::D(int, int)': 
main.cpp:37:16: error: no matching function for call to 'A::A()' 
     C(dataC) { 

请记住,虚拟继承只有一个类型的子对象。并且BC子对象都引用它。您拨打show的电话打印不同的东西是不可能的,因为他们访问的数据是相同的。这就是为什么它取决于最派生的类,所以没有歧义。

[class.mi/4]

甲基类说明不包含关键字虚拟指定非虚拟基类。包含关键字virtual的基类说明符指定了一个虚拟基类。对于大多数派生类的类网格中的非虚拟基类的每个不同的情况,最多派生的对象应包含该类型的相应的不同基类子对象。 对于每个指定为虚拟的不同基类,最大派生对象应包含该类型的单个基类子对象。

[class.base.init/13.1]

在非委托构造函数,以下面的顺序初始化前进:

  • 首先,和仅对最派生类的构造函数,虚拟基类按照它们出现在基类的有向无环图的深度优先从左到右遍历的顺序被初始化为,其中左到右”是在派生类基础说明符列表的基类的出现的顺序。

  • ...

所以,如果你想构建A具体数据您可以定义D::D()这样,而不是:

D(int dataA = 0) : 
    A(dataA) { 
} 
7

当你有virtual继承,virtual基类由大部分派生类的构造函数初始化。

D(int dataB = 0, int dataC = 0) : 
    B(dataB), 
    C(dataC) {} 

等同于:

D(int dataB = 0, int dataC = 0) : 
    A(), 
    B(dataB), 
    C(dataC) {} 

是,在你的情况下,同为

D(int dataB = 0, int dataC = 0) : 
    A(0), 
    B(dataB), 
    C(dataC) {} 

除非你构建的B一个实例,

B(int data = 0) : 
    A(data) { 
} 

是一样的小号

B(int data = 0) {} 

没有代码初始化A因为AD构造已初始化。

同样的事情适用于执行C::C(int data)

这解释了您所看到的输出。

0

我想你误解了继承和'钻石问题'的概念virtual。继承方式为virtual,您创建了钻石继承模式,但是从您的帖子看来,您似乎希望避免使用该模式,而代之以两个基地A,一个来自B,另一个来自C。要获得这一点,只需避免BC中的虚拟继承,当您的代码将写入1 2

顺便说一句,如果仅仅B具有从AC传统的继承virtual继承从A,然后D将有两个基地A,但是从B再次默认初始化(如在其他答案解释)。

相关问题