1. 什么是菱形继承?
单继承:一个子类只有一个直接父类时称这个继承关系为单继承
多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
菱形继承:菱形继承是C++ 多继承的一种表现形式
用一张简单的图就可以说明什么是菱形继承
2. 菱形继承的问题是什么?
继承最简单的理解就是子类将父类中的成员拷贝了一份。我们用最简单的四个类A,B,C,D有且仅有一个成员变量分别是 m_a = 1, m_b = 2, m_c = 3, m_d = 4。B 和 C都继承了 A 类,所以 B 类和 C 类里分别有继承A类的一份 m_a,而D类又同时继承了B和C,这样一来D类里就有了两份 m_a。使得 m_a 在四个类中都存在导致数据冗余;而当D类对象访问类中成员 m_a 时,由于有两份 m_a ,最终导致出现二义性。总结出来菱形继承的问题就是会导致数据冗余和二义性。
3.如何解决菱形继承的问题
解决方法:让 B 虚拟继承 A,C 虚拟继承 A。A 也就被称为虚基类。虚拟继承会让父类 A 的成员在最终子类 D 中只有一份。需要注意的是:虚拟继承不要在其他地方去使用。基本用不到。
实例代码:
class A
{
public:
int m_a = 1;
};
class B : virtual public A
{
public:
int m_b = 2;
};
class C : virtual public A
{
public:
int m_c = 3;
};
class D : public B, public C
{
public:
int m_d = 4;
};
4.虚拟继承解决菱形继承问题的原理
我们可以利用编译器的内存窗口来观察虚拟继承解决数据冗余和二义性的原理。还是利用上面的简单代码来说明问题。
这是没有使用虚拟继承的内存窗口,可以看出有数据 m_a = 1冗余,并且在D类对象访问 m_a 时,计算机不知道是访问 B 里的 m_a 还是 C 里的 m_a ,这样就造成了二义性。
这是使用了虚拟继承的内存窗口,这里可以分析出D对象中将继承的 m_a 放到的了对象组成的最下面,这个 m_a 同时属于B和C。B中的地址 0x0020e1b0 和C 中的地址 0x0020dbbc 其实是两个虚基表指针,分别指向两张虚基表,虚基表中存放有 m_a 的偏移量。计算机可以通过偏移量找到 m_a。这就解决了菱形继承的数据冗余和二义性的问题。
利用虚基表和偏移量找到 m_a 的过程。
B里存放的虚基表指针,指向的虚基表里存放的是如图(14)16 即(20)10的偏移量,在虚拟继承的内存窗口里从B开始数20个字节,刚好访问到 m_a = 1。
C里存放的虚基表指针,指向的虚基表里存放的是如图(0c)16 即(12)10的偏移量,在虚拟继承的内存窗里从C开始数12个字节,也刚好访问到 m_a = 1。