虛表和虛基表的對象模型

繼承這一塊被虛表和虛基表弄的特別暈,專開一篇來研究研究:

首先:虛表和虛基表無關係!!!

虛函數:函數是在類的非靜態成員函數前加virtual,則這個成員函數成爲虛函數(並不是所有的成員函數能夠定義爲虛函數,如構造函數等),在某基類中聲明爲 virtual 並在一個或多個派生類中被重新定義的成員函數,實現多態性。

虛表(虛函數表):C++中的虛函數的實現一般是通過虛函數表(Virtual Table)來實現的。簡稱爲V-Table。 這張表解決了繼承、覆蓋的問題,指明瞭實際所應該調用的函數。虛表可以看做一個指針數組,存放的是所有虛函數的指針,它以NULL來作爲虛表的結束標誌。

虛繼承:C++使用虛擬繼承(Virtual Inheritance),解決從不同途徑繼承來的同名的數據成員在內存中有不同的拷貝造成數據不一致問題,將共同基類設置爲虛基類。這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個拷貝,同一個函數名也只有一個映射。

虛基表:解決菱形繼承產生的二義性的問題,在虛基表中存放的到虛基類的偏移量,虛基表也是以NULL結尾的。

首先,實現一個打印虛表的函數:

typedef void(*FUNC)();
void PrintVTable(int * VTable)
{
	cout << "虛表地址" << VTable << endl;

	int i = 0;
	for (i = 0; VTable[i] != NULL; i++)
	{
		printf("第[%d]個虛函數地址爲 : 0x%p, -> ", i, VTable[i]);

		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}

但是該函數只能適用於32位機器的虛表打印,如果需要既能在32位又能在64位機器下打印的功能:需要將打印函數int*修改int**,並且調用時也應將int*修改爲   :   int**

typedef void(*FUNC)();
void PrintVTable(int** VTable)
{
	cout << "虛表地址" << VTable << endl;

	int i = 0;
	for (i = 0; VTable[i] != NULL; i++)
	{
		printf("第[%d]個虛函數地址爲 : 0x%p, -> ", i, VTable[i]);

		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}

由於我的機器是32位,這裏這裏將使用第一個函數打印虛表。

虛表的內存對象模型:

帶虛表的單繼承模型:

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

class B : public A
{
public:
	virtual void f1()
	{
		cout << "B::f1()" << endl;
	}
	virtual void f3()
	{
		cout << "B::f3()" << endl;
	}
	int _b;
};

int main()
{
	A a;
	a._a = 1;
	PrintVTable((int*)(*(int*)&a));
	B b;
	b._a = 3;
	b._b = 2;
	PrintVTable((int*)(*(int*)&b));
	system("pause");
	return 0;
}

由這張圖可以看出來:

1.子類和父類具有不同的虛表,且子類將父類的虛表複製了下來,只是修改了自己重寫的部分。

2.虛表以NULL結尾。

要打印虛表就要先把虛標的地址取出來:(int*)(*(int*)&b)


因此就可以畫出來帶虛表的單繼承模型:


帶虛表的多繼承模型:

class A
{
public:
	virtual void f1()
	{
		cout << "A::f1()" << endl;
	}
	virtual void f2()
	{
		cout << "A::f2()" << endl;
	}

	int _a;
};

class B
{
public:
	virtual void f1()
	{
		cout << "B::f1()" << endl;
	}
	virtual void f2()
	{
		cout << "B::f2()" << endl;
	}

	int _b;
};

class C : public A, public B
{
public:
	virtual void f1()
	{
		cout << "C::f1()" << endl;
	}
	virtual void f3()
	{
		cout << "C::f3()" << endl;
	}

	int _c;
};

int main()
{
	C c;
	c._a = 1;
	c._b = 2;
	c._c = 3;
	PrintVTable((int*)(*(int*)&c));
	PrintVTable((int*)(*(int*)((char*)&c+ sizeof(A))));
	system("pause");
	return 0;
}


由這幅圖可以看出來:         子類的虛函數添加到了先繼承的類的虛表中。

對虛標進行打印:


由此得出多繼承的對象模型:


虛繼承解決菱形繼承二義性的原理:
class A{
public:
	virtual void fun1()
	{
		cout << "v-A::fun1()" << endl;
	}
	virtual void fun2()
	{
		cout << "v-A::fun2()" << endl;
	}
	
	int _a;
};

class B : virtual public A
{
public:
	virtual void fun1()
	{
		cout << "v-B::fun1()" << endl;
	}
	virtual void fun3()
	{
		cout << "v-B::fun3()" << endl;
	}

	int _b;
};

class C : virtual public A
{
public:
	virtual void fun1()
	{
		cout << "v-C::fun1()" << endl;
	}
	virtual void fun4()
	{
		cout << "v-C::fun4()" << endl;
	}

	int _c;
};

class D : public B, public C
{
public:
	virtual void fun1()
	{
		cout << "v-C::fun1()" << endl;
	}
	virtual void fun5()
	{
		cout << "v-C::fun5()" << endl;
	}
	virtual void fun6()
	{
		cout << "v-C::fun6()" << endl;
	}

	int _d;
};

int main()
{
	D d;
	d.B::_a = 1;
	d._a = 2;
	d.C::_a = 3;
	d._b = 4;
	d._c = 5;
	d._d = 6;

	system("pause");
	return 0;
}


存在菱形繼承時虛繼承的對象模型:


菱形繼承的虛基表(偏移量表):


虛表和虛基表的對象模型圖示:


打印菱形繼承的虛表要注意不能直接加上sizeof(B),因爲B中還包含A.

應使用下面的方式打印:

PrintVTable((int*)(*(int*)((char*)&d + sizeof(B) - sizeof(A))));
PrintVTable((int*)(*(int*)((char*)&d + sizeof(D) - sizeof(A))));


發佈了102 篇原創文章 · 獲贊 21 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章