多态性——vptr和vtable
生活随笔
收集整理的這篇文章主要介紹了
多态性——vptr和vtable
小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.
轉(zhuǎn)自:http://www.sf.org.cn/Article/base/200805/21024.html
多態(tài)性 (polymorphism) 是面向?qū)ο缶幊痰幕咎卣髦?。而?C++ 中,多態(tài)性通過(guò)虛函數(shù) (virtual function) 來(lái)實(shí)現(xiàn)。我們來(lái)看一段簡(jiǎn)單的代碼: #include <iostream> using namespace std; class Base { int a; public: virtual void fun1() {cout<<"Base::fun1()"<<endl;} virtual void fun2() {cout<<"Base::fun2()"<<endl;} virtual void fun3() {cout<<"Base::fun3()"<<endl;} }; class A:public Base { int a; public: void fun1() {cout<<"A::fun1()"<<endl;} void fun2() {cout<<"A::fun2()"<<endl;} }; void foo (Base& obj) { obj.fun1(); obj.fun2(); obj.fun3(); } int main() { Base b; A a; foo(b); foo(a); } 運(yùn)行結(jié)果為: Base::fun1() Base::fun2() Base::fun3() A::fun1() A::fun2() Base::fun3()? 僅通過(guò)基類的接口,程序調(diào)用了正確的函數(shù),它就好像知道我們輸入的對(duì)象的類型一樣! 那么,編譯器是如何知道正確代碼的位置的呢?其實(shí),編譯器在編譯時(shí)并不知道要調(diào)用的函數(shù)體的正確位置,但它插入了一段能找到正確的函數(shù)體的代碼。這稱之為 晚捆綁 (late binding) 或 運(yùn)行時(shí)捆綁 (runtime binding) 技術(shù)。 通過(guò)virtual 關(guān)鍵字創(chuàng)建虛函數(shù)能引發(fā)晚捆綁,編譯器在幕后完成了實(shí)現(xiàn)晚捆綁的必要機(jī)制。它對(duì)每個(gè)包含虛函數(shù)的類創(chuàng)建一個(gè)表(稱為VTABLE),用于放置虛函數(shù)的地址。在每個(gè)包含虛函數(shù)的類中,編譯器秘密地放置了一個(gè)稱之為vpointer(縮寫(xiě)為VPTR)的指針,指向這個(gè)對(duì)象的VTABLE。所以無(wú)論這個(gè)對(duì)象包含一個(gè)或是多少虛函數(shù),編譯器都只放置一個(gè)VPTR即可。VPTR由編譯器在構(gòu)造函數(shù)中秘密地插入的代碼來(lái)完成初始化,指向相應(yīng)的VTABLE,這樣對(duì)象就“知道”自己是什么類型了。 VPTR都在對(duì)象的相同位置,常常是對(duì)象的開(kāi)頭。這樣,編譯器可以容易地找到對(duì)象的VTABLE并獲取函數(shù)體的地址。 如果我們用sizeof查看前面Base類的長(zhǎng)度,我們就會(huì)發(fā)現(xiàn),它的長(zhǎng)度不僅僅是一個(gè)int的長(zhǎng)度,而是增加了剛好是一個(gè)void指針的長(zhǎng)度(在我的機(jī)器里面,一個(gè)int占4個(gè)字節(jié),一個(gè)void指針占4個(gè)字節(jié),這樣正好類Base的長(zhǎng)度為8個(gè)字節(jié))。 每當(dāng)創(chuàng)建一個(gè)包含虛函數(shù)的類或從包含虛函數(shù)的類派生一個(gè)類時(shí),編譯器就為這個(gè)類創(chuàng)建一個(gè)唯一的VTABLE。在VTABLE中,放置了這個(gè)類中或是它的基類中所有虛函數(shù)的地址,這些虛函數(shù)的順序都是一樣的,所以通過(guò)偏移量可以容易地找到所需的函數(shù)體的地址。假如在派生類中沒(méi)有對(duì)在基類中的某個(gè)虛函數(shù)進(jìn)行重寫(xiě)(overriding),那末還使用基類的這個(gè)虛函數(shù)的地址(正如上面的程序結(jié)果所示)。 ?? 至今為止,一切順利。下面,我們的試驗(yàn)開(kāi)始了。 就目前得知的,我們可以試探著通過(guò)自己的代碼來(lái)調(diào)用虛函數(shù),也就是說(shuō)我們要找尋一下編譯器秘密地插入的那段能找到正確函數(shù)體的代碼的足跡。 如果我們有一個(gè)Base指針作為接口,它一定指向一個(gè)Base或由Base派生的對(duì)象,或者是A,或者是其它什么。這無(wú)關(guān)緊要,因?yàn)閂PTR的位置都一樣,一般都在對(duì)象的開(kāi)頭。如果是這樣的話,那么包含有虛函數(shù)的對(duì)象的指針,例如Base指針,指向的位置恰恰是另一個(gè)指針——VPTR。VPTR指向的 VTABLE其實(shí)就是一個(gè)函數(shù)指針的數(shù)組,現(xiàn)在,VPTR正指向它的第一個(gè)元素,那是一個(gè)函數(shù)指針。如果VPTR向后偏移一個(gè)Void指針長(zhǎng)度的話,那么它應(yīng)該指向了VTABLE中的第二個(gè)函數(shù)指針了。 這看來(lái)就像是一個(gè)指針連成的鏈,我們得從當(dāng)前指針獲取它指向的下一個(gè)指針,這樣我們才能“順藤摸瓜”。那么,我來(lái)介紹一個(gè)函數(shù): void *getp (void* p) { return (void*)*(unsigned long*)p; } 我們不考慮它漂亮與否,我們只是試驗(yàn)。getp() 可以從當(dāng)前指針獲取它指向的下一個(gè)指針。如果我們能找到函數(shù)體的地址,用什么來(lái)存儲(chǔ)它呢?我想應(yīng)該用一個(gè)函數(shù)指針: typedef void (*fun)(); 它與Base中的三個(gè)虛函數(shù)相似,為了簡(jiǎn)單我們不要任何輸入和返回,我們只要知道它實(shí)際上被執(zhí)行了即可。 然后,我們負(fù)責(zé)“摸瓜”的函數(shù)登場(chǎng)了: fun getfun (Base* obj, unsigned long off) { void *vptr = getp(obj); unsigned char *p = (unsigned char *)vptr; p += sizeof(void*) * off; return (fun)getp(p); } 第一個(gè)參數(shù)是Base指針,我們可以輸入Base或是Base派生對(duì)象的指針。第二個(gè)參數(shù)是VTABLE偏移量,偏移量如果是0那么對(duì)應(yīng)fun1(),如果是1對(duì)應(yīng)fun2()。getfun() 返回的是fun類型函數(shù)指針,我們上面定義的那個(gè)。可以看到,函數(shù)首先就對(duì)Base指針調(diào)用了一次getp(),這樣得到了vptr這個(gè)指針,然后用一個(gè) unsigned char指針運(yùn)算偏移量,得到的結(jié)果再次輸入getp(),這次得到的就應(yīng)該是正確的函數(shù)體的位置了。 那么它到底能不能正確工作呢?我們修改main() 來(lái)測(cè)試一下: int main() { Base *p = new A; fun f = getfun(p, 0); (*f)(); f = getfun(p, 1); (*f)(); f = getfun(p, 2); (*f)(); delete p; } 激動(dòng)人心的時(shí)刻到來(lái)了,讓我們運(yùn)行它! 運(yùn)行結(jié)果為: A::fun1() A::fun2() Base::fun3() 至此,我們真的成功了。通過(guò)我們的方法,我們獲取了對(duì)象的VPTR,在它的體外執(zhí)行了它的虛函數(shù)。 |
轉(zhuǎn)載于:https://www.cnblogs.com/wangjixianyun/archive/2012/12/21/2827346.html
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎(jiǎng)勵(lì)來(lái)咯,堅(jiān)持創(chuàng)作打卡瓜分現(xiàn)金大獎(jiǎng)總結(jié)
以上是生活随笔為你收集整理的多态性——vptr和vtable的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: DB2 数据库软件下载
- 下一篇: 【OpenCL开发入门】01 - 搭建V