请考虑以下显示多级继承的示例代码:
案例1:此处,类derived1
通过虚拟继承从类base
派生,类derived2
直接从类derived1
派生。 /强>
class base
{
};
class derived1 : virtual public base
{
};
class derived2 : public derived1
{
};
案例2:与案例1相同,但不涉及虚拟继承
class base
{
};
class derived1 : public base // no virtual inheritance
{
};
class derived2 : public derived1
{
};
假设我在两种情况下都创建了类derived2
的对象。
Case1和Case2如何在derived2
对象中包含子对象方面有所不同?
Case1对Case2有意义吗?
PS:我很清楚多重继承过程中虚拟基类的重要性。
答案 0 :(得分:3)
在继承层次结构中没有基类的多个实例时,({1}}基类会考虑(至少)另外两个问题。
首先,虚拟基类始终由正在构建的派生类和非虚拟基类之前初始化。当中间类在其成员初始化列表中将参数传递给虚基类构造函数时,这一点最为明显。这些初始值设定项将被忽略。它也可以改变基类的构造顺序。
其次,无法从虚拟基类到继承自它的类执行virtual
。
答案 1 :(得分:2)
使用虚拟继承时存储了其他信息。这是为了允许动态强制转换在钻石情况下正确解析为派生类。 您的代码没有钻石情况,因此不使用该信息。
如果将数据成员添加到基类,则可以使附加信息可见:
class base {
protected:
int i;
};
如果您现在在两种情况下打印每个派生类的大小,您会发现它们的大小从一个案例到另一个案例有所不同。
编辑:Charles Bailey对使用虚拟继承的语义差异提出了很好的观点。他的第一点特别有趣,应该给予更多考虑。他的观点基本上是derived2
具有隐式菱形拓扑。也就是说,假设derived1
实际上继承自base
:
class base {};
class derived1 : virtual public base {};
然后,derived2
这三个版本之间没有区别,它们的行为与第一个版本相同:
class derived2 : virtual public base, public derived1 {};
class derived2 : public derived1, virtual public base {};
class derived2 : public derived1 {}
这意味着当您从类似derived1
的类派生时(即使用虚拟继承的类)创建了一个隐式菱形。
base
| \
| \.(virtual)
| / \
| /___\
| |
| derived1
| /
| /
|./
/ \
/___\
|
derived2
为了进一步说明这一点,derived1
使用虚拟继承时允许这样做:
class derived2 : public derived1 {
public:
derived2 () : base() {}
};
但是,如果derived1
不使用虚拟继承,则不允许使用。
答案 2 :(得分:1)
虚拟继承在您的情况下没有意义 它仅为钻石继承缩进,如:
B: A
C: A
D: B, C
在这种情况下,两种情况下A的继承都应该是虚拟的。
答案 3 :(得分:1)
只有当类作为基类包含多次时,虚拟继承才会变得很重要。事实上,说明符virtual
在这里意味着“只包含一次”。
如果没有多重继承,则不能多次指定同一个类(循环继承是一个明确的语法错误)。然后谈到编译器中的优化是多么好。如果编译器是完美的,那么没有区别。实际上,您承担了编译器将向您的类添加与多重继承相关的内容的风险。使用该语言的某些功能的次数越少,编译器混淆的风险就越大。编译器通常是一个高质量的程序,但这只是一个程序。