虚继承和基类的基类

3

考虑以下代码:

class A {
};

class B : public A {
};

class C : public B{ 
    public:
    C() : A() {}  // ERROR, A is not a direct base of B
};

在这种情况下,GCC(4.8.1,C++99)会给出正确的错误信息(我理解这种行为): prog.cpp:12:8: error: type ‘a’ is not a direct base of ‘c’
然而,如果b和a之间的继承是虚拟的,则不会发生这种情况:
class A {
};

class B : virtual public A {
};

class C : public B{
    public:
    C() : A() {}  // OK with virtual inheritance
};

为什么这样可以运行呢? 编译器现在会把A看作是C的直接基类吗?

2
不是,但最派生的类会构建一个虚基类。 - T.C.
3个回答

2
一般来说,这是C++试图解决菱形继承问题的方式http://en.wikipedia.org/wiki/Multiple_inheritance#The_diamond_problem(它是否是一个好的或者坏的解决方案留给读者自己思考)。
所有的继承都是is-a和has-a关系的组合...你必须实例化父类。如果你有以下类:
class a;
class b : a;
class c : a;
class d : b,c;

然后你为每个b和c实例化了一个a。d将不知道使用哪个a。
C++通过允许虚拟继承来解决这个问题,这是一种高开销的继承方式,如果在d中继承b和c,则允许它们共享同一个a(比这更复杂,但你可以自己阅读相关资料)。
链中最派生的类型需要能够覆盖共享类的实例化,以控制共享类在父类中继承的差异。以下是一个示例:
class a {int x; public: a(int xx) {x=xx;} int get_x() {return x;}};
class b : public virtual a { public: b(): a(10){}};
class c : public virtual a { public: c(): a(15){}};
class d : public virtual b, public virtual c {public: d() : a (20) {}};
int main() {
    d dd;
    std::cout << dd.get_x() << std::endl;//20, d's constructor "wins"
    return 0;
}

如果d没有定义a的实例化方式,它将会有与b和c中的实例化冲突的定义。C++通过强制继承链中最派生的类来实例化所有父类来处理这个问题(如果d没有明确实例化a,则上述代码会出错,但是如果a提供了默认构造函数,则可以隐式使用),并忽略所有父级实例化。

但如果a有默认构造函数,它就不会出错。 - mip
@doc 的确,会使用默认构造函数进行隐式实例化(并且会忽略 bc 中的 a 的显式实例化)。 - IdeaHat

0

为什么这样可以工作?

根据标准(FIDS中的10.1.4),“对于每个指定为虚拟的不同基类,最派生的对象应该包含一个该类型的单个基类子对象”。

虚拟基类在从它派生的所有类之间共享对象的实例。由于构造函数只能为给定对象的实例调用一次,因此必须在最派生类中显式调用构造函数,因为编译器不知道多少类共享虚拟基类。这是因为编译器将从最基类的构造函数开始,并工作到最派生类。直接继承虚拟基类的类将不会按照标准调用其虚拟基类的构造函数,因此必须显式调用该函数。


为什么你说继承自虚基类的类不会调用基类的构造函数?在我看来,你不需要显式地调用它 :?。http://coliru.stacked-crooked.com/a/ac1ca38005a848d3 - mip
@doc 如果有默认构造函数,则不需要显式实例化它,但它负责实例化:如果在您的构造函数中使用默认构造函数,则无论其他父项中实例化了什么,都会使用该构造函数。 - IdeaHat
@IdeaHat 我不否认你所说的,但是1)默认构造函数是一种构造函数,因此句子“继承...的类将不会调用其虚基类的构造函数...”在我看来没有太多意义(我可能知道ravi的意思,但这并不精确)2)看看OP的问题。他提供了一个只有默认构造函数的代码,所以我是在这个上下文中阅读答案的。 - mip

0

来自N3337,12.6.2

初始化基类和成员

在类的构造函数定义中,可以通过ctor-initializer指定直接和虚拟基类子对象以及非静态数据成员的初始化器,其形式为:

也许有更好版本的标准可以验证这一点。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接