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

delphi 专栏收录该内容
13 篇文章 0 订阅

再谈多态——向上映射及VMT/DMT
作者:Nicrosoft(nicrosoft@sunistudio.com) 2001.10.9
个人主页:http://www.sunistudio.com/nicrosoft/
东日文档:http://www.sunistudio.com/asp/sunidoc.asp
   

    在《浅谈多态——概念描述》一文中,提到多态的本质就是“将子类类型的指针赋值给父类类型的指针”。那么,为什麽这种赋值是允许的,或者说是安全的呢?反过来行不行?虚函数的动态绑定是如何实现的呢?这些问题都将在本文得到解答。
   
    假设有如下代码(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        |-------> |    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;
    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 实例的就不一样了:
    ___________________         ________________
    |    dptr         |-------> |    T2.func1  |
    |    member1      |         |    T2.func2  |
    |    member2      |         ~~~~~~~~~~~~~~~~
    ~~~~~~~~~~~~~~~~~~~
    可以看到,在 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。当然,具体就需要自己把握来选择了。

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
<p> <span style="font-size:14px;color:#337FE5;">【为什么学爬虫?】</span> </p> <p> <span style="font-size:14px;">       1、爬虫入手容易,但是深入较难,如何写出高效率的爬虫,如何写出灵活性高可扩展的爬虫都是一项技术活。另外在爬虫过程中,经常容易遇到被反爬虫,比如字体反爬、IP识别、验证码等,如何层层攻克难点拿到想要的数据,这门课程,你都能学到!</span> </p> <p> <span style="font-size:14px;">       2、如果是作为一个其他行业的开发者,比如app开发,web开发,学习爬虫能让你加强对技术的认知,能够开发出更加安全的软件和网站</span> </p> <p> <br /> </p> <span style="font-size:14px;color:#337FE5;">【课程设计】</span> <p class="ql-long-10663260"> <span> </span> </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 一个完整的爬虫程序,无论大小,总体来说可以分成三个步骤,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 网络请求:模拟浏览器的行为从网上抓取数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据解析:将请求下来的数据进行过滤,提取我们想要的数据。 </li> <li class="" style="font-size:11pt;color:#494949;"> 数据存储:将提取到的数据存储到硬盘或者内存中。比如用mysql数据库或者redis等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 那么本课程也是按照这几个步骤循序渐进的进行讲解,带领学生完整的掌握每个步骤的技术。另外,因为爬虫的多样性,在爬取的过程中可能会发生被反爬、效率低下等。因此我们又增加了两个章节用来提高爬虫程序的灵活性,分别是: </p> <ol> <li class="" style="font-size:11pt;color:#494949;"> 爬虫进阶:包括IP代理,多线程爬虫,图形验证码识别、JS加密解密、动态网页爬虫、字体反爬识别等。 </li> <li class="" style="font-size:11pt;color:#494949;"> Scrapy和分布式爬虫:Scrapy框架、Scrapy-redis组件、分布式爬虫等。 </li> </ol> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 通过爬虫进阶的知识点我们能应付大量的反爬网站,而Scrapy框架作为一个专业的爬虫框架,使用他可以快速提高我们编写爬虫程序的效率和速度。另外如果一台机器不能满足你的需求,我们可以用分布式爬虫让多台机器帮助你快速爬取数据。 </p> <p style="font-size:11pt;color:#494949;">   </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> 从基础爬虫到商业化应用爬虫,本套课程满足您的所有需求! </p> <p class="ql-long-26664262" style="font-size:11pt;color:#494949;"> <br /> </p> <p> <br /> </p> <p> <span style="font-size:14px;background-color:#FFFFFF;color:#337FE5;">【课程服务】</span> </p> <p> <span style="font-size:14px;">专属付费社群+定期答疑</span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"><br /> </span> </p> <p> <br /> </p> <p class="ql-long-24357476"> <span style="font-size:16px;"></span> </p>
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页

打赏

Nicrosoft

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值