85 C++对象模型探索。数据语义学 - 继承多个类,且是虚基类的数据内存模型分析。虚基类表,虚基类表指针

前面我们分析了 继承多个类的情况。上一次分析的这样的情况:

今天看虚基类。先复习一下虚基类:类似下面这样的图

复习虚基类可以解决的问题:

在这之前先要复习一下多继承同一个爷爷类时带来的问题

空间问题

效率问题

二义性问题

//虚基类问题分析
class Teacher14Grand {
public:
	int grandage;
};

class Teacher14Father1 :public Teacher14Grand{

};

class Teacher14Father2 :public Teacher14Grand {

};

class Teacher14 :public Teacher14Father1, public Teacher14Father2 {

};

void main() {
	cout << sizeof(Teacher14Grand) << endl; //4
	cout << sizeof(Teacher14Father1) << endl; //4
	cout << sizeof(Teacher14Father2) << endl; //4
	cout << sizeof(Teacher14) << endl; //8
	//从上述结果可以看到,Teacher14的大小是8。这个大小是8,应该是从Teacher14Fathrer1继承了4个字节,从Teacher14Fathrer2继承了4个字节
	
	//问题:二义性问题,因为Teacher14有的内存布局中是 继承了Teacher14Father1的4个字节,继承了Teacher14Father2的4个字节
	//且Teacher14Father1的4个字节是从Teacher14Grand继承的 int grandage
	//且Teacher14Father2的4个字节是从Teacher14Grand继承的 int grandage
	//因此当我们使用Teacher14的对象 tea给grandage赋值的时候,编译器不知道是给Teacher14Father1中的grandage赋值,还是给Teacher14Father2中的grandage赋值,这个就是二义性问题的原因
	Teacher14 tea;
	//tea.grandage = 80;//像这样写就是有问题的,编译器不知道给那个grandage赋值。
	//fix方案,指定给那个具体的赋值
	tea.Teacher14Father1::grandage = 90;

	//当然,访问的时候,也要指定的访问那一个grandage
	cout << tea.Teacher14Father1::grandage << endl;

	//最开始猜想:由于tea.Teacher14Father2::grandage没有赋过值,因此这个值是乱码
	//但是实际上写这一行的时候,会有build error。提示“使用了未初始化的局部变量tea”
	//cout << tea.Teacher14Father2::grandage << endl;

二义性问题引申:

    //写到这里的时候,进而会想到一个问题。如果子类有和父类同名的变量,在这种case下,会不会也覆盖呢?

class Teacher15Grand {
public:
	int grandage = 80;
};

class Teacher15Father1 :public Teacher15Grand {
public:
	int grandage;
};

class Teacher15Father2 :public Teacher15Grand {

};

class Teacher15 :public Teacher15Father1, public Teacher15Father2 {

};


	//写到这里的时候,进而会想到一个问题。如果子类有和父类同名的变量,在这种case下,会不会也覆盖呢?
	//我们在Teacher15中验证一下。注意:这里不同的是在 Teacher15Grand 中有给grandage初始值为88
	cout << sizeof(Teacher15Grand) << endl; //4
	cout << sizeof(Teacher15Father1) << endl;//8,说明,还是会继承Teacher15Grand中的grandage,然后还有自己的grandage,一共占用8个字节
	cout << sizeof(Teacher15Father2) << endl;//4
	cout << sizeof(Teacher15) << endl;//12

	//那么又有一个问题了,怎么去访问这3个有效的成员呢?
	Teacher15 tea15;
	tea15.Teacher15Father1::grandage = 99; //对于 Teacher15Father1的grandage进行赋值
	tea15.Teacher15Father1::Teacher15Grand::grandage = 199;//对于
	tea15.Teacher15Father2::grandage = 299;

	cout << tea15.Teacher15Father1::grandage << endl;
	cout << tea15.Teacher15Father1::Teacher15Grand::grandage << endl;
	cout << tea15.Teacher15Father2::grandage << endl;

debug代码得到内存图

因此我们可以画出来Teacher14的 内存图

复习虚基类可以解决的问题:

继承爷爷的类使用 virtual 继承即可

class Teacher16Grand {
public:
	int grandage;
};

//让中间类虚继承爷爷
class Teacher16Father1 :public virtual Teacher16Grand {

};

//让中间类虚继承爷爷
class Teacher16Father2 :public virtual Teacher16Grand {

};

class Teacher16 :public Teacher16Father1, public Teacher16Father2 {

};

void main() {
	cout << sizeof(Teacher16Grand) << endl; //4
	cout << sizeof(Teacher16Father1) << endl; //8
	cout << sizeof(Teacher16Father2) << endl; //8
	cout << sizeof(Teacher16) << endl; //12
	//从上面的看到,Teacher16Fa;ther1 和 Teacher16Father2都多了4个字节。
	//这个四个字节是什么呢? 是虚基类表指针

	//引入两个概念 虚基类表,虚基类表指针
	//	只要有虚基类,就会有虚基类表 vbtable-- - virtual base table
	//	如果有子类的对象生成,就会有虚基类表指针 vbptr  virtual base table pointer

	Teacher16 tea;
	tea.grandage = 8;

	cout << "断点在这里" << endl;

}

使用2017开发人员命令试一下:

引出虚基类表,虚基类表指针 概念

只要有虚基类,就会有虚基类表 vbtable --- virtual base table

如果有子类的对象生成,就会有虚基类表指针 vbptr  virtual base table pointer

注意和 虚函数表 和 虚函数指针区分  vtable (virtual table) ,  vptr 也叫做vfptr (virtual  table pointer)

有虚基类和虚函数的同时存在时候 的内存图

内存图中先存储虚基类指针,然后再存储虚函数指针

class Teacher17Grand {
public:
	int grandage;

public:
	void virtual grandfunc() {

	}
};

//让中间类虚继承爷爷
class Teacher17Father1 :public virtual Teacher17Grand {

};

//让中间类虚继承爷爷
class Teacher17Father2 :public virtual Teacher17Grand {

};

class Teacher17 :public Teacher17Father1, public Teacher17Father2 {

};

void main() {
	//那么当既有虚基类表,也有虚函数表的时候,虚基类指针 和虚函数表指针那个放在前面呢?
	cout << sizeof(Teacher17Grand) << endl; //8
	cout << sizeof(Teacher17Father1) << endl; //12
	cout << sizeof(Teacher17Father2) << endl; //12
	cout << sizeof(Teacher17) << endl; //16

	Teacher17 tea;
	tea.grandage = 8;

	cout << "断点在这里" << endl;

}

当虚基类,虚函数,爷爷类,父亲类,子类都有成员变量的时候

class Teacher17Grand {
public:
	int grandage;

public:
	void virtual grandfunc() {

	}
};

//让中间类虚继承爷爷
class Teacher17Father1 :public virtual Teacher17Grand {
public:
	int father1age;
};

//让中间类虚继承爷爷
class Teacher17Father2 :public virtual Teacher17Grand {
public:
	int father2age;
};

class Teacher17 :public Teacher17Father1, public Teacher17Father2 {
public:
	int teaage;
};

void main() {
	//我们接着看,这些成员变量的值都是在哪里存储着?
	cout << sizeof(Teacher17Grand) << endl; // 8  一个vptr+ 一个int
	cout << sizeof(Teacher17Father1) << endl; // 16 一个vbptr  + 一个自己int + 一个vptr + 一个父类 int
	cout << sizeof(Teacher17Father2) << endl; // 16 一个vbptr  + 一个自己int + 一个vptr + 一个父类 int
	cout << sizeof(Teacher17) << endl; // 28 father1vbptr + father1age + father2vbptr + father2age +自己int + 爷爷vptr + 爷爷age

	Teacher17 tea;
	tea.father1age = 1; 
	tea.father2age = 2;
	tea.teaage = 3;
	tea.grandage = 4;

	cout << "断点在这里" << endl;

}

整理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值