钻石继承(笔记整理)

钻石继承(菱形继承)是多继承的特殊形式。钻石继承通常会有二义性,例如:

class A{
	public:
	int m_a=0;
};
class B:public A{
	public:
	int m_b=1;
};
class C:public A{
	public:
	int m_c=2;
};
class D:public B,public C{
	public:
	int m_d=3;
};
int main(){
	D d;
	d.m_d=40;//正确
	d.m_c=20;//正确
	
	//d.m_a=10;//编译错误,对m_a的访问不明确
	return 0;
}

原因是在构造d时会在d中构造B和C,但B和C都有A(m_a).所以说d中有两个m_a,在访问时编译器并不清楚要访问哪个m_a,从而导致程序的二义性。因此,钻石继承的问题其实就是数据的冗余。
上述代码貌似可以改为:

	//d.m_a=10;//编译错误,对m_a的访问不明确
	d.B::m_a=100;
	d.C::m_a=200;
	return 0;

钻石继承(笔记整理)_第1张图片
但这一步显然是有逻辑问题的,程序员实际想要改变的是同一个来自父类A的m_a。
虚拟继承解决了这一问题。

class A{
	public:
	int m_a=0;
};
//class B:public A{
class B:virtual public A{//虚基类
	public:
	int m_b=1;
};
//class C:public A{
class C:virtual public A{
	public:
	int m_c=2;
};
class D:public B,public C{
	public:
	int m_d=3;
};
int main(){
	D d;
	d.m_d=40;//正确
	d.m_c=20;//正确
	
	//d.m_a=10;//编译错误,对m_a的访问不明确
	//改为下面;这样占了两个空间 但是两个m_a并不一样,违背了继承的初衷
	//d.B::m_a=10;
	//d.C::m_a=12;
	//虚拟继承后
	d.m_a=100;//编译正确
	return 0;
}

钻石继承(笔记整理)_第2张图片
观察内存图可以发现,D把A放到了对象组的最下面,这个A同时属于B和C,通过虚继基表指针指向虚继表,虚基表中存储的偏移量(头部),通过偏移量可以找到公共的A空间。

虚继承底层实现原理与编译器相关,一般通过虚基类指针和虚基类表实现,每个虚继承的子类都有一个**虚基类指针**(占用一个指针的存储空间,4字节)和**虚基类表**(不占用类对象的存储空间)(需要强调的是,虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

你可能感兴趣的:(钻石继承(笔记整理))