菱形继承与菱形虚拟继承

菱形继承

  • 单继承:一个子类只有一个直接父类时称这个继承关系为单继承 
  • 多继承:一个子类有两个或以上直接父类时称这个继承关系为多继承

菱形继承与菱形虚拟继承

  • 菱形继承:菱形继承是多继承的一种特殊情况。 

菱形继承与菱形虚拟继承

 

来看看下面这段菱形继承的代码有什么问题呢?

#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。

一张图总结菱形虚拟继承:

菱形继承与菱形虚拟继承