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