菱形继承与菱形虚拟继承
菱形继承
- 单继承:一个子类只有一个直接父类时称这个继承关系为单继承
- 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承
- 菱形继承:菱形继承是多继承的一种特殊情况。
来看看下面这段菱形继承的代码有什么问题呢?
#include <iostream>
class A
{
public:
int a;
};
class A1 : public A
{
public:
int a1;
};
class A2 : public A
{
public:
int a2;
};
class B : public A1, public A2
{
public:
int b;
};
int main()
{
B b;
return 0;
}
这样看好像没有什么问题,我们打开监视窗口发现:
发现b对象中含有两份a,这是因为A1和A2分别继承了A的成员,它们各自都有一份a,B继承了A1A2,所以b对象中就有了两份a,这就造成了数据的冗余性。
若要访问成员a,要怎么访问呢? 看看下面的测试代码:
int main()
{
B b;
// 这样无法明确知道访问的是哪一个,出现了新的问题:二义性
b.a = 1;
// 显示指定访问哪个父类的成员解决了二义性的问题,但是数据冗余问题无法解决
b.A1::a = 1;
b.A2::a = 2;
return 0;
}
总结:菱形继承有数据冗余和二义性的问题!
虚拟继承
虚拟继承可以解决菱形继承的二义性和数据冗余的问题。
上面的关系中,A1和A2继承A时就可以使用虚拟继承,就可以解决这些问题。
class A
{
public:
int a;
};
class A1 : virtual public A //虚拟继承
{
public:
int a1;
};
class A2 : virtual public A //虚拟继承
{
public:
int a2;
};
class B : public A1, public A2
{
public:
int b;
};
注意:虚拟继承不要在其他地方去使用!
到底虚拟继承解决数据冗余和二义性的原理是什么呢?
给出下面的测试代码来看看:
B b;
b.A1::a = 1;
b.A2::a = 2;
b.a1 = 3;
b.a2 = 4;
b.b = 5;
- 来看看菱形继承下的内存:
这里可以看到数据冗余。
- 在看看菱形虚拟继承下的内存:
从内存 1 中可以看到:b对象中 将A放到了最下面,A同时又属于A1和A2,那A1和A2如何找到公共的A呢?
我们又看到内存1中A1和A2中有两个地址(指针),打开内存2查看这个地址发现:它指向一张表,表中第二个元素刚好为A1到A的偏移量。
所以得出:通过了A1和A2的两个指针,各自指向的一张表,这个表叫虚基表,这两个指针叫虚基表指针。虚基表中存的偏移量。通过偏移量可以找到下面的A。
一张图总结菱形虚拟继承: