假設咱們有這樣的一個類:ios
class Base { public: virtual void f() { cout << "Base::f" << endl; } virtual void g() { cout << "Base::g" << endl; } virtual void h() { cout << "Base::h" << endl; } };
按照上面的說法,咱們能夠經過Base的實例來獲得虛函數表。 下面是實際例程:程序員
typedef void(*Fun)(void); Base b; Fun pFun = NULL; cout << "虛函數表地址:" << (int*)(&b) << endl; cout << "虛函數表 — 第一個函數地址:" << (int*)*(int*)(&b) << endl; // Invoke the first virtual function pFun = (Fun)*((int*)*(int*)(&b)); pFun();
實際運行經果以下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)
虛函數表地址:0012FED4
虛函數表 — 第一個函數地址:0044F148
Base::f
經過這個示例,咱們能夠看到,咱們能夠經過強行把&b轉成int *,取得虛函數表的地址,而後,再次取址就能夠獲得第一個虛函數的地址了,也就是Base::f(),這在上面的程序中獲得了驗證(把int* 強制轉成了函數指針)。經過這個示例,咱們就能夠知道若是要調用Base::g()和Base::h(),其代碼以下:算法
(Fun)*((int*)*(int*)(&b)+0); // Base::f() (Fun)*((int*)*(int*)(&b)+1); // Base::g() (Fun)*((int*)*(int*)(&b)+2); // Base::h()
這個時候你應該懂了吧。什麼?仍是有點暈。也是,這樣的代碼看着太亂了。沒問題,讓我畫個圖解釋一下。以下所示:編程
注意:在上面這個圖中,我在虛函數表的最後多加了一個結點,這是虛函數表的結束結點,就像字符串的結束符「/0」同樣,其標誌了虛函數表的結束。這個結束標誌的值在不一樣的編譯器下是不一樣的。在WinXP+VS2003下,這個值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,這個值是若是1,表示還有下一個虛函數表,若是值是0,表示是最後一個虛函數表。 安全
請注意,在這個繼承關係中,子類沒有重載任何父類的函數。那麼,在派生類的實例中,其虛函數表以下所示:網絡
爲了讓你們看到被繼承事後的效果,在這個類的設計中,我只覆蓋了父類的一個函數:f()。那麼,對於派生類的實例,其虛函數表會是下面的一個樣子:編程語言
咱們從表中能夠看到下面幾點,分佈式
Base *b = new Derive(); b->f();
由b所指的內存中的虛函數表的f()的位置已經被Derive::f()函數地址所取代,因而在實際調用發生時,是Derive::f()被調用了。這就實現了多態。函數
對於子類實例中的虛函數表,是下面這個樣子:性能
咱們能夠看到:
下面是對於子類實例中的虛函數表的圖:
咱們能夠看見,三個父類虛函數表中的f()的位置被替換成了子類的函數指針。這樣,咱們就能夠任一靜態類型的父類來指向子類,並調用子類的f()了。如:
Derive d; Base1 *b1 = &d; Base2 *b2 = &d; Base3 *b3 = &d; b1->f(); //Derive::f() b2->f(); //Derive::f() b3->f(); //Derive::f() b1->g(); //Base1::g() b2->g(); //Base2::g() b3->g(); //Base3::g()
安全性
Base1 *b1 = new Derive(); b1->f1(); //編譯出錯
任何妄圖使用父類指針想調用子類中的未覆蓋父類的成員函數的行爲都會被編譯器視爲非法,因此,這樣的程序根本沒法編譯經過。但在運行時,咱們能夠經過指針的方式訪問虛函數表來達到違反C++語義的行爲。(關於這方面的嘗試,經過閱讀後面附錄的代碼,相信你能夠作到這一點)
class Base { private: virtual void f() { cout << "Base::f" << endl; } }; class Derive : public Base{ }; typedef void(*Fun)(void); void main() { Derive d; Fun pFun = (Fun)*((int*)*(int*)(&d)+0); pFun(); }
結束語
C++這門語言是一門Magic的語言,對於程序員來講,咱們彷佛永遠摸不清楚這門語言揹着咱們在幹了什麼。須要熟悉這門語言,咱們就必須要了解C++裏面的那些東西,須要去了解C++中那些危險的東西。否則,這是一種搬起石頭砸本身腳的編程語言。
#include <iostream> using namespace std; class Base1 { public: virtual void f() { cout << "Base1::f" << endl; } virtual void g() { cout << "Base1::g" << endl; } virtual void h() { cout << "Base1::h" << endl; } }; class Base2 { public: virtual void f() { cout << "Base2::f" << endl; } virtual void g() { cout << "Base2::g" << endl; } virtual void h() { cout << "Base2::h" << endl; } }; class Base3 { public: virtual void f() { cout << "Base3::f" << endl; } virtual void g() { cout << "Base3::g" << endl; } virtual void h() { cout << "Base3::h" << endl; } }; class Derive : public Base1, public Base2, public Base3 { public: virtual void f() { cout << "Derive::f" << endl; } virtual void g1() { cout << "Derive::g1" << endl; } }; typedef void(*Fun)(void); int main() { Fun pFun = NULL; Derive d; int** pVtab = (int**)&d; //Base1's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0); pFun = (Fun)pVtab[0][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1); pFun = (Fun)pVtab[0][1]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2); pFun = (Fun)pVtab[0][2]; pFun(); //Derive's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3); pFun = (Fun)pVtab[0][3]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[0][4]; cout<<pFun<<endl; //Base2's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Fun)pVtab[1][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Fun)pVtab[1][1]; pFun(); pFun = (Fun)pVtab[1][2]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[1][3]; cout<<pFun<<endl; //Base3's vtable //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0); pFun = (Fun)pVtab[2][0]; pFun(); //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1); pFun = (Fun)pVtab[2][1]; pFun(); pFun = (Fun)pVtab[2][2]; pFun(); //The tail of the vtable pFun = (Fun)pVtab[2][3]; cout<<pFun<<endl; return 0; }