从内存的角度窥探C++多继承中的菱形继承问题

1. 什么是菱形继承

菱形继承就是继承的方式长得像个菱形而已,它是多继承中一种比较特殊的存在;
比如下面的类关系之间的设计:
student 和 worker 类 继承了 person 类,而 undergraduate 继承了 student 和 worker 这两个类;这个继承的图很像菱形,咋们说它是菱形继承;
在这里插入图片描述


2. 菱形继承会带来什么问题

菱形继承会带来数据访问的二义性和数据冗余的问题;

注意

这个数据访问的二义性是发生在菱形继承最底下的类上;

数据冗余的问题也是发生在菱形继承最底下的类上;


比如:该继承体系下的最底下类的对象模型中:

undergraduate 对象中 m_age就占了两份数据,一份来自 student 和一份来自 worker 的,对于undergraduate来说,是没有必须有这两份数据的,这就导致了 undergraduate类中有数据冗余,也就是 m_age这个数据冗余了;
在这里插入图片描述


而二义性的体现在于:当我定义了一个最底下类的对象时候,也就是Undergraduate ug; ,通过:ug.m_age会导致二义性的存在:也就是不知道访问的到底是student 的m_age 呢 还是 worker的m_age 呢?

虽然我们可以通过:ug.Student::m_age; 和 ug.Woker::m_age 方式来解决数据的二义性问题;但是已经没有解决m_age在ug对象中数据冗余的问题;


代码验证一下:菱形继承带来的二义性和数据冗余的问题:

注意:为了方便说明:我是用 struct 来定义类:因为这是默认成员是共有的,比较容易说明我要讲得问题;

#include<iostream>
using namespace std;

struct Person{
    
    
	int m_age;
};
struct Student:public Person{
    
    
	int m_score;
};
struct Worker:public Person{
    
    
	int m_salary;
};
struct Undergraduate:public Student,public Worker{
    
    
	int m_grade;
};

int main(){
    
    
	Undergraduate ug;
	ug.m_age = 20;
	getchar();
	return 0;
}

编译直接报错:这里就是在验证菱形继承带来的二义性问题;
在这里插入图片描述


解决方案:指明作用域去访问即可:
在这里插入图片描述
这样编译就会成功;


但是二义性解决了,数据冗余问题依旧没解决,ug对象中依旧有两份m_age;并且我们从感性理解,ug对象不需要两份m_age,这个设计本身就是不合理的;

窥探监视窗口:我们客户以看出确实是在ug对象中有两份m_age;这是不合理的设计;在这里插入图片描述


3. 虚继承的方式解决菱形继承数据冗余和二义性的问题

所以基于以上对菱形继承的分析,我们C++ 就有一种虚继承的方式来解决菱形继承带来的问题;
虚继承的方式很简单:我们只要在菱形继承的图中中间部分使用virtual 关键字去虚继承就可以解决这个问题:
在这里插入图片描述

#include<iostream>
using namespace std;

struct Person{
    
    
	int m_age;
};
struct Student:virtual public Person{
    
     //虚继承
	int m_score;
};
struct Worker:virtual public Person{
    
    //虚继承
	int m_salary;
};
struct Undergraduate:public Student,public Worker{
    
    
	int m_grade;
};

int main(){
    
    

	Undergraduate ug;
	ug.m_age = 20; //访问无问题,解决了二义性的问题
	/*ug.Student::m_age = 20;
	ug.Worker::m_age = 30;*/

	getchar();
	return 0;
}

4. 通过内存窥探菱形继承的对象模型

我们通过内存窥探一下ug里面有什么:
在这里插入图片描述


我们可以发现:上面内存的窥探图:可以总结一下的表格查看:

在这里插入图片描述
在这里插入图片描述
这里的20表示:在student的虚表指针,和冗余数据的m_age成员变量,偏移字节数为20;
这里的12便是:在worker的虚表指针,和冗余数据的m_age的成员变量,偏移的字节数为12;
一旦有了虚继承,咋们的m_age就在ug对象只有一份,而访问m_age的方式就是通过上面虚表指针的偏移量去找到的;


通过虚表指针,我们就可以通过偏移量找到了数据冗余的变量m_age,并且解决它冗余且二义性的问题;


猜你喜欢

转载自blog.csdn.net/m0_46606290/article/details/123999031
今日推荐