再谈多态——向上映射及VMT/DMT(转)
生活随笔
收集整理的這篇文章主要介紹了
再谈多态——向上映射及VMT/DMT(转)
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
在《淺談多態(tài)——概念描述》一文中,提到多態(tài)的本質(zhì)就是“將子類類型的指針賦值給父類類型的指
針”。那么,為什麼這種賦值是允許的,或者說是安全的呢?反過來行不行?虛函數(shù)的動態(tài)綁定是如何實
現(xiàn)的呢?這些問題都將在本文得到解答。假設(shè)有如下代碼(Object Pascal語言描述):T1 = classprivatemember1 : integer;publicfunction func1 : Integer; virtual;function func2 : Integer; virtual;function func3 : Integer; virtual;end;T2 = class(T1)privatemember2 : integer;publicfunction func1 : Integer; override;function func2 : Integer; override;end;最終結(jié)果是,T1類的實例的內(nèi)存分布圖如下(僅說明原理,并不表示編譯器一定也是如此實現(xiàn)):___________________ ________________| vptr |-------> | T1.func1 || member1 | | T1.func2 |~~~~~~~~~~~~~~~~~~~ | T1.func3 |~~~~~~~~~~~~~~~~其中,vptr是編譯器自動加入的一個成員指針(稱為虛指針)。只有存在虛函數(shù)或動態(tài)函數(shù)或純虛函
數(shù)的類才會被編譯器加入這個成員指針,該指針指向一個稱為“虛函數(shù)表”(Object Pascal中成為“虛
方法表”——VMT)的內(nèi)存區(qū)域。虛函數(shù)表中,保存了每一個虛函數(shù)的入口地址。T2類的實例的內(nèi)存分布圖如下:___________________ ________________| vptr |-------> | T2.func1 || member1 | | T2.func2 || member2 | | T1.func3 |~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~從圖中我們可以知道,子類對象所占的空間大于父類對象所占空間。因此,當發(fā)生將子類類型的指針
賦值給父類類型的指針的賦值時(即所謂的“向上映射”),也就是父類類型的指針指向了子類類型的對
象所占的內(nèi)存空間,那么,很顯然,可以保證父類類型指針的可訪問范圍都是有效,所以這種“向上映
射”是絕對安全的(所謂“向上”是指類層次的上下關(guān)系,父類在上,子類在下)。這種賦值是得到編譯
器認可的。也可以很容易得出結(jié)論,“向下映射”則未必安全(除非程序員真正知道指針所指對象的實際類
型)。因此,這種賦值是不被編譯器允許的,當然,程序員可以通過類似 T1(Obj) 的形式進行強制類型
轉(zhuǎn)換,但這種強制類型轉(zhuǎn)換很不安全(可以發(fā)生在任何類和類之間),Object Pascal推薦使用 as 算符
進行類型之間的轉(zhuǎn)換,如: (Obj as T1),使用 as 算符,編譯器會檢查對象類型和目標類型是否相容。
如果相容,轉(zhuǎn)換被允許,否則編譯出錯。接著,我們看看虛函數(shù)的動態(tài)綁定是如何實現(xiàn)的。先看如下代碼:procedure Test;var O : T1;beginO := T2.Create;O.func1;O.func3;O.Free;end;看著上面的內(nèi)存布局圖,當執(zhí)行 O := T2.Create; 后,一個 T1 類型的指針指向 T2 實體。執(zhí)行
O.func1 時,編譯器通過 vptr 找到虛函數(shù)表,在虛函數(shù)表中定位到了 T2.func1(由于 T1.func1 被
“覆蓋”了,因此虛函數(shù)表中找不到 T1.func1),于是,T2.func1 被調(diào)用,這就是動態(tài)綁定!但由于
T2 沒有重寫 func3,因此 O.func3 將調(diào)用 T1.func3,這一點在虛函數(shù)表中也可以很明顯看出來。好了,說到這里,我想動態(tài)綁定已經(jīng)說的非常清楚了,說明一點,本文雖然以 Object Pascal代碼為
例,但其原理對于 C++也同樣有效。C++與Object Pascal(甚至不同C++編譯器之間)的區(qū)別僅在于類成
員及vptr在內(nèi)存中分布的位置而已。那么,最后再談一下 Object Pascal 獨有的 DMT(動態(tài)方法表)吧。在VMT中,我們看到,子類的虛
函數(shù)表完全繼承了父類的虛函數(shù)表,只是將被覆蓋了的虛函數(shù)的地址改變了。每個子類都有一份自己的虛
函數(shù)表,可以想象,隨著類層次的擴展,如果類層次非常深,或者子類的數(shù)量非常多的話,虛函數(shù)表將稱
為占用內(nèi)存量非常大的東西(即所謂的“類爆炸”)。為了防止這種情況, Object Pascal 引入了
DMT。對于程序員來說,區(qū)別僅在于使用“dynamic”關(guān)鍵字代替“virtual”關(guān)鍵字,所實現(xiàn)的功能也完
全一樣。如果把本文開頭的那段代碼重寫如下(用 dynamic 代替 virtual):T1 = classprivatemember1 : integer;publicfunction func1 : Integer; dynamic;function func2 : Integer; dynamic;function func3 : Integer; dynamic;end;T2 = class(T1)privatemember2 : integer;publicfunction func1 : Integer; override;function func2 : Integer; override;end;那么,T1 的內(nèi)存分布圖沒有改變,而 T2 實例的就不一樣了:___________________ ________________| dptr |-------> | T2.func1 || member1 | | T2.func2 || member2 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~可以看到,在 T2 的動態(tài)方法表中,沒有被覆蓋的 T1.func3 消失了。因此:procedure Test;var O : T1;beginO := T2.Create;O.func3;O.Free;end;O.func3 這一句代碼將被編譯器做更多的處理:找到 T1 類的 func3 函數(shù)的入口地址,然后再調(diào)
用。比較一下 VMT 和 DMT 的區(qū)別:VMT 中的虛函數(shù)非常齊全,因此對每個虛函數(shù)的入口地址只需要簡單的 [vptr + n] 的運算即可得
到,但是 VMT 容易消耗內(nèi)存(有冗余)。而 DMT 比較節(jié)省空間,但要定位到?jīng)]有被覆蓋的函數(shù)的入口地
址時,將非常耗費時間。一般情況下,幾乎每個子類都要覆蓋的函數(shù)/方法,就將它聲明為 virtual;如果類層次很深,或子
類很多,但某個函數(shù)/方法只被很少的子類覆蓋,就將它聲明為 dynamic。當然,具體就需要自己把握來
選擇了。
轉(zhuǎn)載于:https://www.cnblogs.com/keycode/archive/2010/10/15/1852386.html
總結(jié)
以上是生活随笔為你收集整理的再谈多态——向上映射及VMT/DMT(转)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery使用总结 - Core jQ
- 下一篇: 音乐文件基本格式,wave,mod,mi