探索虛函數與多態



測試環境:win10 64位 vs2013

一些概念

       虛函數:類的成員函數前面加上virtual關鍵字,則此成員函數即爲虛函數。

       重寫:在子類定義了一個與父類完全相同的虛函數,則稱子類的虛函數重寫了父類的虛函數。

       多態:多態就是多種形態,C++的多態分爲靜態多態和動態多態。靜態多態就是重載,因爲在編譯期間決定調用哪個函數,所以稱爲靜態多態;動態多態是通過繼承重寫基類的虛函數實現的多態,因爲是在運行期間決定調用哪個函數,所以稱爲動態多態。

1.單繼承對象模型

#pragma once

#include <iostream>

using namespace std;

class Base
{
public:
	virtual void f1()
	{
		cout << "Base::f1" << endl;
	}
	virtual void f2()
	{
		cout << "Base::f2" << endl;
	}
private:
	int _a;
};
class Derive :public Base
{
public:
	virtual void f1()
	{
		cout << "Derive::f1" << endl;
	}
	virtual void f3()
	{
		cout << "Derive::f3" << endl;
	}

private:
	int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
	cout << "虛表地址:" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d個虛函數地址 :0%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}
void test1()
{
	Base b1;
	Derive d1;
	int* VTable1 = (int*)(*(int*)&b1);
	int* VTable2 = (int*)(*(int*)&d1);
	PrintVTable(VTable1);
	PrintVTable(VTable2);
}

單繼承原理:

       對於b1: 在構造時,爲所有虛函數創建一個虛表,虛表首地址存在對象b1的首地址中。

       對於d1: 在構造時,同樣要建立一個虛表,由於繼承了父類;所以首先構造父類內成員,然後再構造d1內其他成員,在構造父類後,由於存在重寫f1。所以將d1中f1覆蓋到父類f1位置,以下爲測試。

內存分佈:

       運行代碼發現在監視窗口並沒有顯示d1完整的虛函數地址,打開內存窗口,輸入d1的虛表地址


上述數據基本符合預期,爲了更直觀,將上述虛函數地址打印,如下:

現在在Base裏添加兩個Display()函數,一個傳參,一個不傳,使他們構成重載。

void test2()
{
	Derive d2;
	Base& b2 = d2;
	b2.f1();
	b2.Display();
	b2.Display(1);
}

查看反彙編,明顯得出動態多態與靜態在尋址時極爲不同的方式

b2.f1();                ;動態多態,虛表尋找地址

mov     eax,dword ptr [b2]       ;b2地址給eax

mov     edx,dword ptr [eax]     ;eax指向的內容給edx

mov     esi,esp 

mov     ecx,dword ptr [b2]       ;b2地址給eax

mov     eax,dword ptr [edx]     ;edx內容給eax

call      eax 

cmp     esi,esp 

call     __RTC_CheckEsp (01B1361h) 

b2.Display();       ;靜態多態,編譯時確定地址

mov     ecx,dword ptr [b2] 

call      Base::Display (01B124Eh) 

b2.Display(1);

push        1 

       b2.Display(1);

mov       ecx,dword ptr [b2] 

call       Base::Display (01B10E1h)

2.多重繼承對象模型


#include <iostream>

using namespace std;

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

	virtual void f2()
	{
		cout<<"Base1::f2"<<endl;
	}

private:
	int _b1;
};

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

	virtual void f2()
	{
		cout<<"Base2::f2"<<endl;
	}

private:
	int _b2;
};


class Derive: public Base2,public Base1
{
public:
	virtual void f1()
	{
		cout<<"Derive::f1"<<endl;
	}

	virtual void f3()
	{
		cout<<"Derive::f3"<<endl;
	}

private:
	int _d1;
};

typedef void(*FUNC)();
void PrintVTable(int* VTable)
{
	cout << "虛表地址:" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d個虛函數地址 :0x%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}
void test1()
{
	Derive d;
	PrintVTable((int*)(*((int*)&d)));
	PrintVTable((int*)(*((int*)((char*)&d + sizeof(Base1)))));
}


多重繼承原理:

       在重多繼承過程中,子類的成員函數重寫(上例f1),如果兩個父類同時包含重寫的函數,將子類的其他虛函數存在優先繼承的虛函數表中(上例先繼承Base2)。

內存分佈:

打開監視窗口仍然發現不能顯示全部虛函數的地址,如下:

然後我們可以打印出虛函數地址,如下:

於是我們得到多重繼承模型:

3.菱形虛擬繼承

#include <iostream>

using namespace std;

class A
{
public:
	virtual void f1()
	{}

	int _a;
};

class B : virtual public A
{
public:
	virtual void f1()
	{}

	virtual void f2()
	{
		cout << "D::f2()" << endl;
	}

	int _b;
};
class C : virtual public A
{
public:
	virtual void f1()
	{}

	virtual void f2()
	{
		cout << "D::f2()" << endl;
	}

	int _c;
};

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

	virtual void f3()
	{
		cout << "D::f3()" << endl;
	}

	int _d;
};

void test1()
{
	D d1;
	d1._a = 1;
	d1._b = 2;
	d1._c = 3;
	d1._d = 4;
	PrintVTable((int*)(*(int*)&d1));
	PrintVTable((int*)(*((int*)&d1 + 3)));		//+3是因爲用sizeof計算崩潰
	PrintVTable((int*)(*((int*)&d1 + 7)));		//所以對照內存表人工計算
}

通過查看內存分配和打印每個虛表內函數地址,畫出如下d1模型圖,由於虛繼承將A構造的對象,放置在了最下面,然後有一虛基表指向此處,以後可以訪問














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