C++类模型漫谈(五)

系统基于32位,MSVC编译器,VS开发工具

1、上篇讲到菱形继承,我们发现菱形继承会导致有两份的基类TypeA子对象,因为TypeB继承了TypeA,TypeC也继承了TypeA ,所以TypeB子对象和TypeC子对象都会包含TypeA部分。

可以通过虚继承保持只有一个TypeA子对象,至于如何只出现一份,看下文。

先来个简单的虚继承TypeB虚继承TypeA。

虚继承相对于普通继承,内存中将TypeA子对象放在了b_obj内存的后面部分,而之前普通继承则直接是放在了前面。

现在导致TypeA子对象部分的__vfptr和TypeB部分的__vfptr无法合并。

我们可以看到TypeA的__vfptr的虚表只包含了TypeA定义的2个函数,当然有一个被TypeB重写了,所以虚表中存的是&TypeB::TypeA_Method

TypeB的__vfptr的虚表只包含了TypeB自己定义的那1个函数TypeB::TypeB_Method1,虚表存的是&TypeB::TypeB_Method1

通过这个无法合并的两个虚表,我们看到每个类型的虚表只包含了自己定义的虚函数指针,哪怕重写过或者继承,也不会包含在内

另外我们看到TypeB部分还包含了另一个编译器生成的变量__vbptr  (virtual Base Pointer 虚基类表指针),指向一个虚基类偏移表,偏移表存的是相对于每一个虚基类的偏移位置。另外一方面说明,如果只是普通继承,不会往表中存放偏移位置

当然如果一个类没有任何的虚基类,则编译器也不会为其生成__vbptr。

TypeB虚继承TypeA,而TypeA子对象部分地址比TypeB对象起始地址多了16个字节,所以偏移表中存的16,占4个字节

如果TypeB再虚继承TypeC,  而TypeC子对象部分地址比TypeB对象起始地址多24字节,那边偏移表中则会再添加一条数据24(在数据16的后4个字节中存放)

class TypeA {
public:
	char a1 = 10;
	int a2 = 20;
	void virtual TypeA_Method1() {
		cout << "TypeA::TypeA_Method1" << endl;
	}
	void virtual TypeA_Method2() {
		cout << "TypeA::TypeA_Method2" << endl;
	}
};
class TypeB:public virtual TypeA {
public:
	char b1 = 30;
	int b2 = 40;	
	void virtual TypeA_Method1() {//重写了TypeA中的TypeA_Method1
		cout << "TypeB::TypeA_Method1" << endl;
	}
	void virtual TypeB_Method1() {//TypeB类型自己的函数TypeB_Method1
		cout << "TypeB::TypeB_Method1" << endl;
	}
};
int main()
{   
	TypeB b_obj;
	TypeB* b_ptr = &b_obj;
	TypeA* a_ptr = b_ptr;
	b_ptr->TypeA_Method1();//TypeB指针调用TypeA_Method1
	b_ptr->TypeA_Method2();//TypeB指针调用TypeA_Method2
	a_ptr->TypeA_Method1();//TypeA指针调用TypeA_Method1

	return 1;
}

1、通过a_ptr调用TypeA_Method1()  过程也是差不多,要获得a_ptr也是要先通过b_ptr的虚基类偏移表,转换获得a_ptr指针,接下来一样通过a_ptr获取__vfptr并定位到&Type::TypeA_Method1调用,这个时候需要的是TypeB类型对象,则传入b_ptr作为this指针。

2、通过b_ptr调用TypeA_Method2() 跟图例的函数调用过程一样,只不过最后传入函数的this指针为b_ptr+16,即TypeA子对象的地址,因为该函数没有被重写过还是TypeA::TypeA_Method2。

posted @ 2022-10-26 09:26  自由小菜园  阅读(17)  评论(0编辑  收藏  举报