在C++中,多態表示 “以一個公有基類的指針或引用,尋址出一個派生類對象” 。
假如有調用 ptr->get_c() ,其中ptr是基類指針,get_c()是一個虛函數。要在執行期能正確調用get_c()的實例,我們需要知道:
1.ptr所指對象的真正類型,以便我們選擇正確的get_c()實例。
2.get_c()實例的位置,以便我們能夠調用他。
在實現上,編譯時期會構建出來一張虛表,表格中有程序的虛函數的執行期地址。
為了找到這個表格,每一個類對象被安插一個由編譯器產生的指針,指向虛表。
為了找到函數地址,每一個虛函數被指派一個表格索引值。
如果有代碼例子:
class A { protected: int a; public: A(const int a) :a(a){} virtual ~A(); virtual int pure_v() = 0; //純虛函數 virtual int get_b(){ return 0; } virtual int get_c(){ return 0; } }; class B :public A { protected: int b; public: B(int a = 0, int b = 0):A(a),b(b){} ~B(); int pure_v(); int get_b(){ return b; } }; class C :public B { protected: int c; public: C(int a = 0, int b = 0, int c = 0) :B(a, b), c(c){} ~C(); int pure_v(); int get_c(){ return c; } };
一個類只會有一張虛表。每個表內含其對應類對象中三類虛函數,這三類包括:
1.這一類所定義的虛函數實例或改寫自基類的虛函數實例。如例子中Calss B中的get_b()。
2.繼承自基類的已被改寫過的函數實例。如例子中Calss C的虛表中會有get_b(),在C中不改寫,而在B中已被改寫過。
3.一個純虛函數的實例。如Class B中的pure_v()。
接下來我們看類A,B,C的虛表布局。
對於A a; 布局如下:(博主是用visio畫的,有點丑- -)
_vptr是編譯時期產生的虛表指針。
slot 0通常是指出每個類所關聯的type_info object(用以支持RTTI)。
A的虛析構被指派為solt 1。純虛函數被指派為solt 2,但A中的純虛函數沒有定義,如果意外調用了此函數,通常的操作是結束這個程序。
get_b()被指派slot ,get_c()被指派slot 4。
對於B:A b; 布局如下:
B的虛表中在solt 1指出析構函數。A中的純虛函數有了定義,所以在slot 2指出pure_()。
自己的get_b()函數實例地址放在solt 3。繼承自A的get_c()函數實例地址放在solt 4。
對於C:B c; 布局如下:
solt 1放置析構函數地址,solt 2放置pure_v()函數地址,solt 3放置繼承自B的get_b()的函數地址,solt 4放置自己的get_c()函數地址。
現在再來看調用ptr->get_c();
*在每次調用get_c()時,我們並不知道ptr所指對象的真正類型。但是我知道通過ptr可以存取到該對象的虛表。
*雖然我不知道哪一個get_c()函數實例會被調用,但是我知道每一個get_c()的函數實例地址都被放在solt 4中。
通過這些信息,編譯器可以把調用改寫為:
(ptr->_vptr[4])(ptr);
從而在執行期調用正確的函數實例。