分享
 
 
 

再谈多态—向上映射及VMT/DMT

王朝other·作者佚名  2008-06-01
窄屏简体版  字體: |||超大  

版权所有: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类的实例的内存分布图如下(仅说明原理,并不表示编译器一定也是如此实现):

其中,vptr是编译器自动加入的一个成员指针(称为虚指针)。只有存在虚函数或动态函数或纯虚函数的类才会被编译器加入这个成员指针,该指针指向一个称为“虚函数表”(Object Pascal中成为“虚方法表”——VMT)的内存区域。虚函数表中,保存了每一个虚函数的入口地址。

T2类的实例的内存分布图如下:

从图中我们可以知道,子类对象所占的空间大于父类对象所占空间。因此,当发生将子类类型的指针赋值给父类类型的指针的赋值时(即所谓的“向上映射”),也就是父类类型的指针指向了子类类型的对象所占的内存空间,那么,很显然,可以保证父类类型指针的可访问范围都是有效,所以这种“向上映射”是绝对安全的(所谓“向上”是指类层次的上下关系,父类在上,子类在下)。这种赋值是得到编译器认可的。

也可以很轻易得出结论,“向下映射”则未必安全(除非程序员真正知道指针所指对象的实际类型)。因此,这种赋值是不被编译器答应的,当然,程序员可以通过类似 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 实例的就不一样了:

可以看到,在 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。当然,具体就需要自己把握来选择了。

进入讨论组讨论。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有