菱形虚拟继承的原理

发布于:2025-03-25 ⋅ 阅读:(19) ⋅ 点赞:(0)

一 :菱形继承的问题

 

普通的菱形继承存在数据冗余和二义性的问题 ,如下代码:

class Person {
public:
	string _name; //姓名
};
 
class Student : public Person {
protected:
	int _num; //学号
};
 
class Teacher : public Person {
protected:
	int _id; //编号
};
 
class Assistant : public Student, public Teacher {
protected:
	string _majorCourse; //主修课程
};
 
void Test() {
	
	Assistant a;
	a._name = "peter";//这样会有二义性无法明确知道访问的是哪一个

 
	// 需要显示指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决
	a.Student::_name = "xxx";
	a.Teacher::_name = "yyy";
}

解释:

显示地指定访问哪个父类的成员可以解决二义性问题,但是数据冗余问题无法解决 ,所以有了虚拟菱形继承

二:虚拟菱形继承

class Person {
public:
	string _name;
	int _hugeArr[10000];
};
 
// 虚继承
class Student : virtual public Person {
protected:
	int _num;
};
 
// 虚继承
class Teacher : virtual public Person {
protected:
	int _id;
};
 
class Assistant : public Student, public Teacher {
protected:
	string _majorCourse;
};

在类腰部位置加一个 virtual 关键字

虚拟继承不但解决数据冗余,还解决了二义性

为何请看三~

三:虚拟菱形继承的原理

 

为了研究虚拟继承原理,我们给出了一个简化的菱形继承继承体系,再借助 内存窗口观察对象成
员的模型。

1:非虚拟菱形继承的内存分布

//类A
class A
{
public:
  int _a;
};


//B虚拟继承A
class B :  public A
{
public:
  int _b;
};


//C虚拟继承A
class C :  public A
{
public:
  int _c;
};


//D再继承B和A
class D : public B, public C
{
public:
  int _d;
};



int main()
{
 D d;

//对成员变量的赋值
 d.B::_a = 1;
 d.C::_a = 2;
 d._b = 3;
 d._c = 4;
 d._d = 5;

 return 0;
}

借助内存窗口观察:

可以发现:

①:d这个对象中存放了所有的变量,无论是继承和还是自己的

②:1和3紧挨因为类B中是这两个成员变量,2和4紧挨着因为类C中是这两个成员变量

③:d的成员变量放在最后

2:虚拟菱形继承的内存分布

//类A
class A
{
public:
  int _a;
};


//B虚拟继承A
class B : virtual public A
{
public:
  int _b;
};


//C虚拟继承A
class C : virtual public A
{
public:
  int _c;
};


//D再继承B和A
class D : public B, public C
{
public:
  int _d;
};



int main()
{
 D d;

//对成员变量的赋值
 d.B::_a = 1;
 d.C::_a = 2;
 d._b = 3;
 d._c = 4;
 d._d = 5;

 return 0;
}

借助内存窗口观察:

解释:_a不再存在两份,变成只有一份

Q:类B和类C中的第一行存放的地址有什么意义?

A:解释如图

总结:虚继承中,B和C类中,并没有直接存放类A,类A被存放在了一个公共的位置,在类B和C中,额外存放了一个地址,该地址指向的地方为虚基表,各自的虚基表中能得到各自的类和类A的相隔字节数

Q:为什么这么的麻烦?不直接把相隔的字节数直接存放在类B和类C中?却要存放一个地址,改地址指向的虚基表中才有相隔的字节数

A:因为虚基表中不止仅仅有相隔字节数这一信息,很多信息暂时还没到学的时候,而类B和类C中只有4字节的位置,所以存在虚基表的地址刚刚好

一个需要虚基表的场景:

B* pb = &d;//d是一个已有类D的对象
pb->_a++;

解释:在代码中的切片赋值的时候,我们只保留了类B的部分,此时我们要在pb中找到A的成员变量,通过类B中的虚基表即可