C++菱形继承和菱形虚拟继承关系的虚函数表详解

菱形继承中的虚函数表

class A {
public:
	virtual void func1() { cout << "A::func1" << endl; }
private:
	int _a;
};

class B :public A{
public:
	virtual void func1() { cout << "B::func1" << endl; }
	virtual void func2() { cout << "B::func2" << endl; }
private:
	int _b;
};
class C :public A{
public:
	virtual void func1() { cout << "C::func1" << endl; }
	virtual void func2() { cout << "C::func2" << endl; }
private:
	int _c;
};
class D : public B, public C {
public:
	virtual void func1() { cout << "D::func1" << endl; }
	virtual void func3() { cout << "D::func3" << endl; }
private:
	int _d;
};

typedef void(*VFPTR)();//虚函数指针
void Print(VFPTR *arr)
{
	for (int i = 0; arr[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址:%0x\n",i, arr[i]);
		arr[i]();
	}
}

int main()
{
	D d;
	VFPTR* arr1 = (VFPTR*)(*(int*)&d);//虚表指针(虚函数指针数组)
	VFPTR* arr2 = (VFPTR*)(*(((int*)&d)+3));//第二个虚表指针偏移量
	Print(arr1);
	Print(arr2);
	return 0;
}	
  • 如下图,d对象是有两个虚表的
    在这里插入图片描述
  • 两个虚表中都不一定只有一个虚函数,vs在这里是无法看到子类新增加的虚函数。
  • 我们将第一个虚表地址所在的地址加上偏移量即可求得第二个虚表地址的地址。将他们虚表内容打印出来或者运行虚函数。
    在这里插入图片描述
  • 可以看出菱形继承派生类的新增加的虚函数放在第一个继承基类部分的虚函数表中。
  • 子类重写Func1时,把B和C两个类中的虚函数都重写了。

菱形虚拟继承中的虚函数表

class A {
public:
	virtual void func1() { cout << "A::func1" << endl; }
private:
	int _a=1;
};

class B :virtual public A{
public:
	virtual void func1() { cout << "B::func1" << endl; }
	virtual void func2() { cout << "B::func2" << endl; }
private:
	int _b=2;
};
class C :virtual public A{
public:
	virtual void func1() { cout << "C::func1" << endl; }
	virtual void func2() { cout << "C::func2" << endl; }
private:
	int _c=3;
};
class D : public B, public C {
public:
	virtual void func1() { cout << "D::func1" << endl; }
	virtual void func3() { cout << "D::func3" << endl; }
private:
	int _d=4;
};
  • 如下图是对象d的结构
    在这里插入图片描述
  • 这样看起来很难受,太乱了,无法具体判断。
  • 我们可以根据菱形继承的模型来推导菱形虚拟继承的结构模型
  • 下图是菱形继承的模型
    在这里插入图片描述
  • 根据上图,我们可以推导出菱形虚拟继承的结构模型如下
    在这里插入图片描述
  • 因为A类中的Func1()函数和_a变量都是冗余的,因此在虚拟继承中都会放在对象组成的最下面,他们同时属于B和C.
  • B和C则通过虚基表指针,通过偏移量找到_a。
  • 至于_a和A::Func1()谁先放在对象组成的后面,我们可以通过内存看见是A::Func1()先放在对象组成的后面。
    在这里插入图片描述
  • 下面我们通过代码来验证一下
typedef void(*VFPTR)();//虚函数指针
void Print(VFPTR *arr)
{
	for (int i = 0; arr[i] != nullptr; ++i)
	{
		printf("第%d个虚函数地址:%0x\n",i, arr[i]);
		arr[i]();
	}
}

int main()
{
	D d;
	VFPTR* vfptr1 = (VFPTR*)(*(int*)&d);//第一个虚表
	VFPTR* vfptr2 = (VFPTR*)(*(((int*)&d) + 3));//第二个虚表
	VFPTR* A_Func1 = (VFPTR*)(*(((int*)&d) + 7));//A::Func1()在对象组成后面的函数指针
	
	Print(vfptr1);
	Print(vfptr2);

	printf("A::Func1()地址:%0x\n", (int*)A_Func1);
	(*A_Func1)();//调用函数
	
	return 0}

在这里插入图片描述

  • 我们可以看出来两个虚表中都没有了Func1()的函数指针,也的确是放在对象组成的下面。消除了二义性。
发布了161 篇原创文章 · 获赞 52 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_42837885/article/details/102985580