国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

再談多態—向上映射及VMT/DMT

2019-11-17 04:38:45
字體:
來源:轉載
供稿:網友
版權所有:Nicrosoft
文章來源:東日制作室
在《淺談多態——概念描述》一文中,提到多態的本質就是“將子類類型的指針賦值給父類類型的指針”。那么,為什麼這種賦值是答應的,或者說是安全的呢?反過來行不行?虛函數的動態綁定是如何實現的呢?這些問題都將在本文得到解答。

假設有如下代碼(Object Pascal語言描述):

T1 = class
PRivate
member1 : integer;
public
function func1 : Integer; virtual;
function func2 : Integer; virtual;
function func3 : Integer; virtual;
end;

T2 = class(T1)
private
member2 : integer;
public
function func1 : Integer; override;
function func2 : Integer; override;
end;

最終結果是,T1類的實例的內存分布圖如下(僅說明原理,并不表示編譯器一定也是如此實現):

再談多態—向上映射及VMT/DMT

其中,vptr是編譯器自動加入的一個成員指針(稱為虛指針)。只有存在虛函數或動態函數或純虛函數的類才會被編譯器加入這個成員指針,該指針指向一個稱為“虛函數表”(Object Pascal中成為“虛方法表”——VMT)的內存區域。虛函數表中,保存了每一個虛函數的入口地址。

T2類的實例的內存分布圖如下:

再談多態—向上映射及VMT/DMT

從圖中我們可以知道,子類對象所占的空間大于父類對象所占空間。因此,當發生將子類類型的指針賦值給父類類型的指針的賦值時(即所謂的“向上映射”),也就是父類類型的指針指向了子類類型的對象所占的內存空間,那么,很顯然,可以保證父類類型指針的可訪問范圍都是有效,所以這種“向上映射”是絕對安全的(所謂“向上”是指類層次的上下關系,父類在上,子類在下)。這種賦值是得到編譯器認可的。

也可以很輕易得出結論,“向下映射”則未必安全(除非程序員真正知道指針所指對象的實際類型)。因此,這種賦值是不被編譯器答應的,當然,程序員可以通過類似 T1(Obj) 的形式進行強制類型轉換,但這種強制類型轉換很不安全(可以發生在任何類和類之間),Object Pascal推薦使用 as 算符進行類型之間的轉換,如: (Obj as T1),使用 as 算符,編譯器會檢查對象類型和目標類型是否相容。
  假如相容,轉換被答應,否則編譯出錯。 進入討論組討論。

接著,我們看看虛函數的動態綁定是如何實現的。先看如下代碼:

procedure Test;
var O : T1;
begin
O := 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 = class
private
member1 : integer;
public
function func1 : Integer; dynamic;
function func2 : Integer; dynamic;
function func3 : Integer; dynamic;
end;

T2 = class(T1)
private
member2 : integer;
public
function func1 : Integer; override;
function func2 : Integer; override;
end;

那么,T1 的內存分布圖沒有改變,而 T2 實例的就不一樣了:

再談多態—向上映射及VMT/DMT

可以看到,在 T2 的動態方法表中,沒有被覆蓋的 T1.func3 消失了。因此:

procedure Test;
var O : T1;
begin
O := T2.Create;
O.func3;
O.Free;
end;

O.func3 這一句代碼將被編譯器做更多的處理:找到 T1 類的 func3 函數的入口地址,然后再調
用。

比較一下 VMT 和 DMT 的區別:

VMT 中的虛函數非常齊全,因此對每個虛函數的入口地址只需要簡單的 [vptr + n] 的運算即可得到,但是 VMT 輕易消耗內存(有冗余)。而 DMT 比較節省空間,但要定位到沒有被覆蓋的函數的入口地址時,將非常耗費時間。

一般情況下,幾乎每個子類都要覆蓋的函數/方法,就將它聲明為 virtual;假如類層次很深,或子類很多,但某個函數/方法只被很少的子類覆蓋,就將它聲明為 dynamic。當然,具體就需要自己把握來選擇了。
進入討論組討論。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 肥乡县| 南澳县| 文山县| 保靖县| 江陵县| 黑山县| 阆中市| 长子县| 景德镇市| 河北区| 泗洪县| 常熟市| 大新县| 吴川市| 梓潼县| 夏津县| 保定市| 炉霍县| 安义县| 微博| 克拉玛依市| 临沭县| 故城县| 建德市| 屏南县| 乡城县| 镇康县| 宝鸡市| 江川县| 中西区| 鸡泽县| 新河县| 民和| 普兰店市| 武隆县| 平潭县| 临漳县| 将乐县| 南澳县| 重庆市| 二连浩特市|