了的虚方法的地址改变了。基类和每个派生类都有一份自己的虚方法表。可以想象,随着
类层次的扩展,虚方法表将耗费非常大的内存空间。为了防止这种情况,Object Pascal引
入了“dynamic”的概念。对于程序员来说,dynamic方法和virtual方法实现相同的功能,
只是声明的关键字不同:
Procedure fly(); dynamic; // 是dynamic而不是virtual
被声明为dynamic的方法,其入口地址将被放在DMT中。DMT和VMT的区别在于:
对于派生类没有覆盖的方法,这些方法的入口地址不会出现在DMT中,编译器要通过基
类的信息来寻找它们的入口地址。
如果将TPlane的抽象方法land和虚方法modal改成dynamic,即:
TPlane = class
protected
FModal : String;
public
procedure fly(); virtual; abstract; // 仍然保持virtual
procedure land(); dynamic; abstract; // 将virtual改成dynamic
function modal() : string; dynamic; // 将virtual改成dynamic
…… // 其他可能的操作
end;
TCopter = class(TPlane)
public
constructor Create();
destructor Destroy(); override;
procedure fly(); override;
procedure land(); override;
function modal() : string; // 不覆盖Plane.modal();
…… // 其他可能的操作
end;
则TCopter的VMT/DMT会变成如图2.7所示的样子。
对比图2.6和图2.7可知,由于DMT中不会出现没有被派生类覆盖的基类dynamic方
法,因此DMT会比VMT节省空间(大多数情况下)。当基类有许多虚方法,而派生类只
覆盖很少几个时,区别尤其明显。当派生层次越来越深,派生类数量越来越多,DMT就能
节省更多的内存空间。但是DMT中对基类的动态方法的寻址不是直接进行的,因此dynamic
方法的寻址比virtual方法要慢许多。
Plane指针 指向VMT的指针
FModal
直升机类的VMT
直升机对象实例
TCopter.land()
……
指向DMT的指针
……
……
……
TCopter.fly()
直升机类的DMT
……
DMT中没有了未被覆
盖的TPlane.modal()
图2.7 直升机类的VMT/DMT
virtual和dynamic的区别仅在于编译器采用不同的晚绑定策略而已,对于程序员来说,
它们的功能相同。
如何取舍就看实际的需求了,一般情况下,几乎每个派生类都要覆盖的方法,将它声
明为virtual;如果类层次很深,或派生类很多,但某个方法只被很少的派生类覆盖,则将
它声明为dynamic。
另外需要注意的是,只有VMT才与C++、COM的vtable兼容,因此当需要这样的兼
容性时,只能使用virtual。
2.5 小 结
传统的说法是,封装、继承、多态是面向对象编程的三个基本特性。实际上,封装只
是抽象数据类型(ADT),有了继承才能被称为面向对象。而继承的存在,除了扩展现存
类的功能外,另一个更重要的作用就是作为多态存在的基石。
多态是一种能够带来灵活性的东西,它使得通过接口重用来实现代码重用。可以毫不
夸张地说,不领会多态,不明白晚绑定,就不可能明白什么是面向对象!因为只有在会用
virtual后,才是真正在用面向对象的典范(paradigm)思考……