目录
一. 菱形继承
1.1 什么是菱形继承
要明白什么是菱形继承,首先要明确单继承和多继承的概念:
- 单继承:每个子类只有一个直接父类的继承体系。
- 多继承:一个子类有两个或两个以上父类的继承体系。

菱形继承其实就是一种特殊的多继承,在菱形继承体系中,某一个子类含有两个或两个以上的父类,这个子类的父类又共同继承另外一个类。

演示代码1.1:(图1.2所示菱形继承体系的实现)
class A
{
public:
int _a1;
};
class B : public A
{
public:
int _b1;
};
class C : public A
{
public:
int _c1;
};
class D : public B, public C
{
public:
int _d1;
};
1.2 菱形继承体系的数据存储模型
在演示代码1.1的继承体系中,D对象中包含C对象和B对象,B、C对象中又都包含一份A对象,为了能通过调试观察D类对象中成员变量数据在内存中的存储,编写了演示代码1.2,调试代码,打开内存监视窗口监视。基于内存监视窗口的结果,我们总结出了如图1.4所示的菱形继承数据存储模型。
演示代码1.2:
int main()
{
D d;
d.B::_a1 = 1;
d._b1 = 2;
d.C::_a1 = 3;
d._c1 = 4;
d._d1 = 5;
return 0;
}


1.3 菱形继承体系中存在的缺陷
- 二义性的问题
如图1.2所示的菱形继承体系,D类对象中会存储两份_a1成员,如果试图直接通过d._a1来访问_a1成员变量,则无法确定是B类对象还是C类对象中的成员变量。二义性问题,可以通过指定作用域限定操作符::指定作用域来避免。具体的语法格式为:d.B::_a1;
- 数据冗余问题
由于d中存有两份_a1,但大部分情况下菱形继承中只需要有一个_a1变量即可,另外一个是多余的,这里可以使用虚继承来解决问题,在定义类B和类C时,使用virtual关键字声明虚继承来避免数据冗余问题。
二. 菱形虚拟继承
2.1 菱形虚拟继承的作用及定义方法
在第1.3章节中提到,直接使用菱形继承会存在数据冗余的问题,而使用菱形虚拟继承的作用就是避免数据冗余。同时,菱形虚拟继承还能避免二义性的问题,使用d._a1不存在不知道访问那个_a1的情况。
总结菱形虚拟继承的作用:
- 避免数据冗余,使派生类对象中名称相同的变量存储一份。
- 避免二义性,类对象中没有了同名变量,使用d._a1访问某个成员变量就不存在无法明确访问谁的问题。
虚拟继承定义方法:在菱形继承体系中的腰部类,即两个或两个以上继承同一父类的类的位置处,使用virtual声明虚拟继承即可。

演示代码2.1:定义菱形虚拟继承的方法
class A
{
public:
int _a1;
};
class B : virtual public A
{
public:
int _b1;
};
class C : virtual public A
{
public:
int _c1;
};
class D : public B, public C
{
public:
int _d1;
};
2.2 菱形虚拟继承中的数据存储模型
调试演示代码2.2,打开内存监视窗口观察数据在内存中的存储情况。
演示代码2.2:
int main()
{
D d;
d.B::_a1 = 1;
d.C::_a1 = 2;
d._b1 = 3;
d._c1 = 4;
d._d1 = 5;
return 0;
}

通过观察图2.2,我们可以发现,B和C的公共部分A被提取到了最下方的位置,这个A同属于B和C,也可以单独使用d._a1来访问,在存储B和C的成员变量的同时存储了两个指针变量,这两个指针成为虚基指针,两个虚基指针各指向一张存储了B对象和C对象相对与A的偏移量的表,这张表称为虚基表,通过A相对于B、C存储位置的偏移量,来找到A。
