多型進階
- 1. 多型實作原理探究
- 1.1 包含虛函式的類的大小
- 1.2 虛函式表
- 1.3 動態系結與靜態系結
- 2. 單繼承中的虛函式表
- 3. 多繼承中的虛函式表
1. 多型實作原理探究
1.1 包含虛函式的類的大小
求sizeof(Base)的大小?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
正常來說,Base類中只用一個int型成員變數,其大小應該是4位元組,但是經過VS2013編譯后顯示,其實是 8位元組 ,
我使用VS2013中的監視功能發現:Base類的實體化變數中多了一個
void**指標變數_vfptr,這個指標指向的東西是一個虛函式表,
1.2 虛函式表
- 一個含有虛函式的類中都至少都有一個
虛函式表指標,因為虛函式的地址要被放到虛函式表中,虛函式表也簡稱虛表 - 物件中的這個指標_vfptr我們叫做虛函式表指標(v代表virtual,f代表function)

- 通過匯編代碼可以發現,_vfptr中保存的是虛擬函式的地址
觀察以下代碼
// 1.我們增加一個派生類Derive去繼承Base
// 2.Derive中重寫Func1
// 3.Base再增加一個虛函式Func2和一個普通函式Func3
class Base
{
public:
virtual void Func1(){}
virtual void Func2(){}
void Func3(){}
private:
int _b = 1;
};
class Derive : public Base
{
public:
virtual void Func1(){}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}

黃色框框:我們可以發現,每個物件中有自己獨立的虛函式表指標
紅色框框:我們可以發現,子類重寫父類的虛函式后,重寫后的虛函式Func1就是一個新的虛函式了
藍色框框:我們可以發現,子類繼承了父類的虛擬函式Func2,子類并沒有重寫該函式,故子類物件的虛函式表指標中存放的依然是父類的虛函式Func2的地址
給Derive類加入一個虛擬函式Func4后發現在VS2013的監視的_vfptr中并未顯示出Func4的函式入口地址,但是在_vfptr的地址中保存著三個虛擬函式地址

【總結】
- 虛函式表本質是一個存虛函式指標的指標陣列,這個陣列最后面放了一個nullptr,
- 派生類的虛表生成:a.先將基類中的虛表內容拷貝一份到派生類虛表中 b.如果派生類重寫了基類中某個虛函式,用派生類自己的虛函式覆寫虛表中基類的虛函式 c.派生類自己新增加的虛函式按其在派生類中的宣告次序增加到派生類虛表的最后,
- 虛函式和普通函式一樣都是存在于代碼段的
1.3 動態系結與靜態系結
靜態系結:稱為前期系結或早系結,在程式編譯期間就確定了程式的行為,稱為靜態多型,例如:函式多載動態系結:稱為后期系結或晚系結,在程式運行期間才確定的程式的行為,稱為動態多型
class Person {
public:
virtual void BuyTicket() { cout << "買票-全價" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "買票-半價" << endl; }
};
void Func(Person& p)
{
p.BuyTicket();
}
int main()
{
Person Mike;
Func(Mike);
Student Johnson;
Func(Johnson);
return 0;
}
輸出:
買票-全價 買票-半價
匯編代碼解釋:
// 以下匯編代碼中跟你這個問題不相關的都被去掉了
void Func(Person* p)
{
p->BuyTicket();
// p中存的是mike物件的指標,將p移動到eax中
001940DE mov eax,dword ptr [p]
// [eax]就是取eax值指向的內容,這里相當于把mike物件頭4個位元組(虛表指標)移動到了edx
001940E1 mov edx,dword ptr [eax]
// [edx]就是取edx值指向的內容,這里相當于把虛表中的頭4位元組存的虛函式指標移動到了eax
00B823EE mov eax,dword ptr [edx]
// call eax中存虛函式的指標,這里可以看出滿足多型的呼叫,不是在編譯時確定的,是運行起來以后到物件的中取找的,
001940EA call eax
001940EC cmp esi,esp
}
int main()
{
...
// 首先BuyTicket雖然是虛函式,但是mike是物件,不滿足多型的條件,所以這里是普通函式的呼叫轉換成地址時,是在編譯時已經從符號表確認了函式的地址,直接call 地址
mike.BuyTicket();
00195182 lea ecx,[mike]
00195185 call Person::BuyTicket (01914F6h)
...
}
2. 單繼承中的虛函式表
取出類中的 虛函式表
class Base {
public:
virtual void func1() { cout << "Base::func1" << endl; }
virtual void func2() { cout << "Base::func2" << endl; }
private:
int a;
};
class Derive :public Base {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
virtual void func4() { cout << "Derive::func4" << endl; }
private:
int b;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
// 依次取虛表中的虛函式指標列印并呼叫,呼叫就可以看出存的是哪個函式
cout << " 虛表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d個虛函式地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Base b;
Derive d;
// 思路:取出b、d物件的頭4bytes,就是虛表的指標,前面我們說了虛函式表本質是一個存虛函式指標的指標陣列,這個陣列最后面放了一個nullptr
// 1.先取b的地址,強轉成一個int*的指標
// 2.再解參考取值,就取到了b物件頭4bytes的值,這個值就是指向虛表的指標
// 3.再強轉成VFPTR*,因為虛表就是一個存VFPTR型別(虛函式指標型別)的陣列,
// 4.虛表指標傳遞給PrintVTable進行列印虛表
// 5.需要說明的是這個列印虛表的代碼經常會崩潰,因為編譯器有時對虛表的處理不干凈,虛表最后面沒有放nullptr,導致越界,這是編譯器的問題,我們只需要點目錄欄的 - 生成 - 清理解決方案,再編譯就好了,
VFPTR* vTableb = (VFPTR*)(*(int*)&b);
PrintVTable(vTableb);
VFPTR* vTabled = (VFPTR*)(*(int*)&d);
PrintVTable(vTabled);
return 0;
}
輸出:
虛表地址>00BFDC74 第0個虛函式地址 :0Xbf100a,->Base::func1 第1個虛函式地址 :0Xbf1285,->Base::func2 虛表地址>00BFDCA4 第0個虛函式地址 :0Xbf11ae,->Derive::func1 第1個虛函式地址 :0Xbf1285,->Base::func2 第2個虛函式地址 :0Xbf11fe,->Derive::func3 第3個虛函式地址 :0Xbf111d,->Derive::func4
圖解單繼承中虛函式表

3. 多繼承中的虛函式表
class Base1 {
public:
virtual void func1() { cout << "Base1::func1" << endl; }
virtual void func2() { cout << "Base1::func2" << endl; }
private:
int b1;
};
class Base2 {
public:
virtual void func1() { cout << "Base2::func1" << endl; }
virtual void func2() { cout << "Base2::func2" << endl; }
private:
int b2;
};
class Derive : public Base1, public Base2 {
public:
virtual void func1() { cout << "Derive::func1" << endl; }
virtual void func3() { cout << "Derive::func3" << endl; }
private:
int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{
cout << " 虛表地址>" << vTable << endl;
for (int i = 0; vTable[i] != nullptr; ++i)
{
printf(" 第%d個虛函式地址 :0X%x,->", i, vTable[i]);
VFPTR f = vTable[i];
f();
}
cout << endl;
}
int main()
{
Derive d;
VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
PrintVTable(vTableb1);
VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
PrintVTable(vTableb2);
return 0;
}
輸出:
虛表地址>00D9DCD4 第0個虛函式地址 :0Xd911ae,->Derive::func1 第1個虛函式地址 :0Xd913f2,->Base1::func2 第2個虛函式地址 :0Xd911fe,->Derive::func3 虛表地址>00D9DCE8 第0個虛函式地址 :0Xd91249,->Derive::func1 第1個虛函式地址 :0Xd91311,->Base2::func2
圖解多繼承中虛函式表

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/261083.html
標籤:其他
下一篇:Hive架構(資料庫和表)


