Bjarne Stroustrup写道:
在为具有虚拟基类的类定义函数时,程序员通常无法知道基类是否将与其他派生类共享。当实现一个只需要调用一次基类函数的服务时,这可能是一个问题。
我不明白这句话。 哪些问题可能会蔓延?
为了解释它,他给出了一个奇怪的例子
class A { // no constructor
// ...
};
class B {
public:
B(); // default constructor
// ...
};
class C {
public:
C(int); // no default constructor
};
class D: virtual public A, virtual public B, virtual public C
{
D() { /*... */ } // error: no default constructor for C
D(int i) : C(i i) { /*... */ }; // ok
// ...
};
这里有关系吗?
答案 0 :(得分:2)
想象一下,你有一个虚拟基类需要通过调用.initialize(42)
来初始化,只需要一次。关键是,你没有哪个派生类应该调用它。
struct X : virtual A {
X() {
// here
}
};
struct Y : virtual A {
Y() {
// or here
}
};
struct Z : X, Y {
// what about this
};
在这种情况下,正确的答案是“有时在这里,有时在那里”,但你不知道是哪种情况(取决于哪个类是派生最多的子对象)。
C ++为构造函数解决了这个问题。所有虚拟基础的构造函数在最派生的子对象的构造函数中只调用一次;层次结构中的任何构造函数都必须准备好调用虚拟基础构造函数(尽管在运行时可能不会发生)。
答案 1 :(得分:1)
想象一下这样的情况:
struct Base
{
Base() { }
virtual void call_me();
};
struct A : virtual Base { A() { call_me(); } };
struct B : virtual Base { B() { call_me(); } };
struct Derived : A, B
{
Derived()
: Base() // virtual base is construced in most-derived
, A()
, B()
{ }
};
现在警告是关于中间类A
和B
可能会假设它们是唯一调用Base::call_me()
的事实。如果Base
的继承是非虚拟的,那确实是这种情况,因为A
和B
都有自己唯一的基类。但是,使用虚拟继承,不能 A
和B
来确定谁最终成为他们的基类,因为只有最终的类Derived
安装所有中间人共享的实际单基类。因此,在我们的示例中, A
和B
最终在相同的基础对象上调用call_me()
。
道德是,与虚拟基类相关的操作应该是派生的类的责任。由于这不是一个严格的概念,并且遗传实际上可以无限延长,这是一个滑坡。虚拟,多重继承是一个复杂的概念。