作為物件導向程式設計的四大特徵(抽象、封裝、繼承和多型)之一的多型性,是考察開發人員對物件導向的基礎掌握程度的有力知識點。面試中也經常被問到像這樣的問題:請簡述一下什麼是多型?c++多型的實現機制是什麼?等等之類。今天我們就來小談一下c++的多型機制。
首先,我們先開宗明義:
在c++中,多型性通過虛函式 (virtual function) 來實現。
那麼什麼又是虛函式呢?有些文章又說:
虛函式是c++中用於實現多型(polymorphism)的機制。這不等於沒說嘛。這裡我們不追本溯源了:關於虛函式更學術級的定義大家可以參閱《
c++ primer
》裡的說明,這裡只要記住一點:
所謂虛函式就是在編譯的時候不確定要呼叫哪個函式,而是動態決定將要調 用哪個函式,要實現虛函式必須派生類的函式名與基類相同,引數名引數型別等也要與基類相同。但派生類中的virtual關鍵字可以省略。
c++中的多型性,其
核心理念就是通過基類訪問派生類定義的函式。
**伺候:
#include
using namespace std;
class base ;
class a:public base ;
void foo (base& obj)
int main()
執行結果為:
僅通過基類的介面,程式呼叫了正確的函式,它就好像知道我們輸入的物件的型別一樣!
那麼,編譯器是如何知道正確**的位置的呢?其實,編譯器在編譯時並不知道要呼叫的函式體的正確位置,但它插入了一段能找到正確的函式體的**。這稱之為 晚** (late binding) 或 執行時** (runtime binding) 技術。
通過virtual 關鍵字建立虛函式能引發晚**, 編譯器在幕後完成了實現晚**的必要機制。它對每個包含虛函式的類建立乙個表(稱為vtable),用於放置虛函式的位址。在每個包含虛函式的類中,編譯器秘密地放置了乙個稱之為vpointer(縮寫為vptr)的指標,指向這個物件的vtable。所以無論這個物件包含乙個或是多少虛函式,編譯器都只放置乙個vptr即可。vptr由編譯器在建構函式中秘密地插入的**來完成初始化,指向相應的vtable,這樣物件就「知道」自己是什麼型別了。vptr都在物件的相同位置,常常是物件的開頭。這樣,編譯器可以容易地找到物件的vtable並獲取函式體的位址。
如果我們用sizeof檢視前面base類的長度,我們就會發現,它的長度不僅僅是乙個int的長度,而是增加了剛好是乙個void指標的長度(在我的機器裡面,乙個int佔4個位元組,乙個void指標佔4個位元組,這樣正好類base的長度為8個位元組)。
每當建立乙個包含虛函式的類或從包含虛函式的類派生乙個類時,編譯器就為這個類建立乙個唯一的vtable。在vtable中,放置了這個類中或是它的基類中所有虛函式的位址,這些虛函式的順序都是一樣的,所以通過偏移量可以容易地找到所需的函式體的位址。假如在派生類中沒有對在基類中的某個虛函式進行重寫(overriding),那末還使用基類的這個虛函式的位址(正如上面的程式結果所示)。
至今為止,一切順利。下面,我們的試驗開始了。
就目前得知的,我們可以試探著通過自己的**來呼叫虛函式,也就是說我們要找尋一下編譯器秘密地插入的那段能找到正確函式體的**的足跡。
如果我們有乙個base指標作為介面,它一定指向乙個base或由base派生的物件,或者是a,或者是其它什麼。這無關緊要,因為vptr的位置都一樣,一般都在物件的開頭。如果是這樣的話,那麼包含有虛函式的物件的指標,例如base指標,指向的位置恰恰是另乙個指標——vptr。vptr指向的vtable其實就是乙個函式指標的陣列,現在,vptr正指向它的第乙個元素,那是乙個函式指標。如果vptr向後偏移乙個void指標長度的話,那麼它應該指向了vtable中的第二個函式指標了。
這看來就像是乙個指標連成的鏈,我們得從當前指標獲取它指向的下乙個指標,這樣我們才能「順藤摸瓜」。那麼,我來介紹乙個函式:
void *getp (void* p)
我們不考慮它漂亮與否,我們只是試驗。getp() 可以從當前指標獲取它指向的下乙個指標。如果我們能找到函式體的位址,用什麼來儲存它呢?我想應該用乙個函式指標:
typedef void (*fun)();
它與base中的三個虛函式相似,為了簡單我們不要任何輸入和返回,我們只要知道它實際上被執行了即可。
然後,我們負責「摸瓜」的函式登場了:
fun getfun (base* obj, unsigned long off)
第乙個引數是base指標,我們可以輸入base或是base派生物件的指標。第二個引數是vtable偏移量,偏移量如果是0那麼對應fun1(),如果是1對應fun2()。getfun() 返回的是fun型別函式指標,我們上面定義的那個。可以看到,函式首先就對base指標呼叫了一次getp(),這樣得到了vptr這個指標,然後用乙個unsigned char指標運算偏移量,得到的結果再次輸入getp(),這次得到的就應該是正確的函式體的位置了。
那麼它到底能不能正確工作呢?我們修改main() 來測試一下:
int main()
激動人心的時刻到來了,讓我們執行它!
執行結果為:
至此,我們真的成功了。通過我們的方法,我們獲取了物件的vptr,在它的體外執行了它的虛函式。
0 給主人留下些什麼吧!~~
C 的多型性實現機制剖析
c 的多型性實現機制剖析 即 vc this 指標詳細說明 2006年1 月12日星期四 我們先看乙個例子 例 1 1 include class animal void breathe class fish public animal void main 注意,在例 1 1的程式中沒有定義虛函式。...
C 的多型性實現機制剖析
1 多型性和虛函式 我們先看乙個例子 考慮一下這段程式的輸出結果是什麼?答案是輸出 animal breath 我們在main函式中首先定義乙個fish類的物件fh,接著定義了乙個指向animal類的指標pan,將fn的位址賦給了指標變數pan,然後利用該變數呼叫pan breath 許多人往往將這...
C 的多型性實現機制剖析
我們先看乙個例子 例 1 1 include class animal voidbreathe class fish public animal void main 注意,在例 1 1的程式中沒有定義虛函式。考慮一下例 1 1的程式執行的結果是什麼?答案是輸出 animal breathe 我們在m...