`
liuzhaomin
  • 浏览: 207700 次
  • 性别: Icon_minigender_1
  • 来自: 北京
文章分类
社区版块
存档分类
最新评论

解析动态联编(下篇)

阅读更多
三 虚函数表VTABhttp://www.vckbase.comLE

动态联编过程跟我们猜测的大致相同。编译器在执行过程中遇到virtual关键字的时候,将自动安装动态联编需要的机制,首先为这些包含virtual函数的类(注意不是类的实例)--即使是祖先类包含虚函数而本身没有--建立一张虚拟函数表VTABLE。在这些虚拟函数表中,编译器将依次按照函数声明次序放置类的特定虚函数的地址。同时在每个带有虚函数的类中放置一个称之为vpointer的指针,简称vptr,这个指针指向这个类的VTABLE。
关于虚拟函数表,有几点必须声明清楚:
1. 每一个类别只能有一个虚拟函数表,如果该类没有虚拟函数,则不存在虚拟函数表。
2. C++编译时候编译器会在含有虚函数的类中加上一个指向虚拟函数表的指针vptr。
3. 从一个类别诞生的每一个对象,将获取该类别中的vptr指针,这个指针同样指向类的VTABLE。

因此类、对象、VTABLE的层次结构可以用下图表示。其中X类和Y类的对象的指针 都指向了X,Y的虚拟函数表,同时X,Y类自身也包含了指向虚拟函数的指针。

为了方便问题说明,我们将2.cpp例子进行扩展,扩展程序如下。
//4.cpp
15. #include <iostream.h >
16. class shape{
17.  public:
18.    virtual void draw(){cout<<"shape::draw()"<<endl;}
19.    virtual void area(){cout<<"shape::area()"<<endl;}
20.    void fun(){draw();area();}
21. };
22. class circle:public shape{
23.  public:
24.    void draw(){cout<<"circle::draw()"<<endl;}
25.    void adjust(){cout<<"circle::adjust()"<<endl;}
26. };
27. main(){
28.    shape oneshape;
29.    oneshape.fun();
30.    
31.    circle  circleshape;
32.    shape&  baseshape=circleshape;
33.    baseshape.fun();
34. }
编译器在编译上面这段代码的时候将为这shape和circle两个对象分别建立一个VTABLE表,这些表依次填充派生类对象和基类对象中声明的所有的虚函数地址。如果派生类本身没有重新定义基类的虚函数,那么填充的就是基类的虚函数地址。这样一旦如果函数调用一个派生类不存在的方法时候能够自动调用基类方法。然后编译器在每个类中放置一个vptr,一般置于对象的起始位置,继而在对象的构造函数中将vptr初始化为本类的VTABLE的地址。整个结果布局如下。

图一

图一中的rectangle的VTABLE中的area() 和triangle的VTABLE的adjust()都是填充的基类的虚函数地址。 C++ 编译程序时候按下面的步骤进行工作:
①为各类建立虚拟函数表,如果没有虚函数则不建立。
②暂时不连接虚函数,而是将各个虚函数的地址放入虚拟函数表中。
③直接连接各静态函数。
这些工作做完之后,模块图如图二:

图二

执行时候,诞生了oneshape和circleshape两个对象,oneshape对象的vptr指针指向shape的VTABLE,circleshape对象的vptr指针指向circleshape的VTABLE,在执行oneshape.fun()的时候,fun函数的this指针指向了oneshape对象,进入fun()之后程序继续执行this->draw(),由于this指向oneshape对象,oneshape的vptr又指向shape类的VTABLE,这样就从VTABLE中得到需要绑定的函数的地址,并连接起来。同样,this-> area()也经由oneshape对象而连接到相应的函数上,如图三。

图三

现在我们执行baseshape.fun()函数。
circle  circleshape;
shape&  baseshape=circleshape;
baseshape.fun();
函数进入fun函数之后,函数的this指针将指向basefun对象,另一方面basefun指向一个circleshape,因此this指针指向的实际上为circleshape对象,而circleshape的vptr指针指向circle类的虚拟函数表,这样编译器将从虚拟表中取出circle::draw()和circle::area()的地址,进行连接。因为circle本身没有重新定义area()方法,因此编译器使用shape的area()方法。如图四。

图四

遵循上面的思路,基于基类的指针总能找到正确的子类对象的实现。但是象上面的 this->draw是怎么编译的呢。

四 编译内幕

在上面的程序中,this指针不同,从而连接到不同的fun函数。那么C++如何编译这些指令呢。道理在于:所有的基类的派生类的虚拟函数表的顺序与基类的顺序是一样的,对于基类中不存在方法再按照声明次序进行排放。这样不管是shape还是circle或者从shape又继承出来的其余的类它们的虚拟函数表的第一项总是draw函数的地址,然后是area的地址。对于circle类,下面的才是adjust的地址。因此不管对于shape还是circle,this->draw总是编译成 call this->VTABLE[0]; this->area()总是翻译成 call this->VTABLE[1]; 程序到真正运行时候将会发现this的真正指向的对象,如果是shape,则调用shape->VTABLE[0],如果是circle,则调用circle->VTABLE[1],如图五。

图五

请看下面的这个例子。
35. #include <iostream.h></iostream.h>
36. class shape{
37.  public:
38.    virtual void draw(){cout<<"shape::draw()"<draw();//OK
51.    oneshape->adjust();//错误,编译器无法通过
52.    
53.    circle* circleshape;
54.    circleshape->adjust();
55. }

在程序编译期间,由于oneshape为shape类型的,因此它将检查shape的虚拟函数表,发现VTABLE[0]为draw函数的地址,于是翻译成p->VTABLE[0]。未来执行期间,p 实际上指向的是circle对象,因此真正调用的为circle->VTABLE[0]处的函数,即circle::draw。同样对于adjust函数,C++ 编译器也会去检查shape的VTABLE,结果编译器无法找到adjust函数,因此编译无法通过。 对于circleshape,因为它是circleshape类型的,因此它将会检查circle的VTABLE,得知VTABLE[2]处为adjust的地址,因此编译器翻译成call circleshape->VTABLE[2],真正执行时候circleshape为circle类型,因此它将绑定circle的VTABLE[2]处的函数即circle:: adjust()。 就这样,编译器借助虚拟函数表实现了动态联编的过程,从而使多态的实现有了可能。因此说虚拟函数表是多态性的幕后功臣一点也不为过。

五 结束语

多态性的实现是一个非常复杂的过程,上面的讨论仅仅是针对简单继承而言,即基类只有一个的情况,对于多重继承,情况又会有所改变。本文仅是抛砖引玉,希望有兴趣的朋友可以一起探讨。

分享到:
评论

相关推荐

    入库单的制作解析PPT教案学习.pptx

    为了深入解析入库单的制作流程,本篇PPT教案将提供详尽的解说和操作指导。 首先,我们需认识到入库单在仓储管理中的核心作用。作为货物进入仓库并成为库存的法律依据,入库单上记录的信息将对后续的库存管理和物流...

    河北省承德市联校 高二英语下学期期末考试试题(扫描版) 试题.doc

    标题和描述中提到的是“河北省承德市联校 高二英语下学期期末考试试题(扫描版) 试题.doc”,这显然是一份针对中国高中二年级学生的英语期末考试试卷,由河北省承德市的联合学校编制。这份资料可能是教师用于备考、...

    C++面向对象程序设计第05章.ppt

    2. **运行时的多态性** 主要是通过**虚函数**(Virtual Function)实现,这是一种动态联编(Dynamic Binding)的技术。虚函数允许子类继承基类并覆盖基类的同名函数,当通过基类指针或引用调用这个函数时,实际执行...

    部编本人教版八年级上册语文《与朱元思书》ppt课件12

    ### 部编本人教版八年级上册语文《与朱元思书》知识点解析 #### 一、文体知识及背景介绍 - **文体特点**: - **骈文**:盛行于南北朝时期的特殊文体,以四字句、六字句相互交替,因此又称“四六文”。骈文的主要...

    湖北省武汉市钢城第四中学2019-2020学年高二数学下学期5月考试试题(PDF)

    这篇文档是湖北省武汉市钢城第四中学2019-2020学年高二数学下学期5月的考试试题,包含单选题和填空题。以下是试题中涉及的知识点解析: 1. 组合计数:第一题是组合问题,计算三层书架上不同取书方法的总数,涉及到...

    有关会计述职报告模板合集八篇.docx

    ### 会计述职报告关键知识点解析 #### 一、会计述职报告概述 会计述职报告是会计人员根据自身在一定时期内的工作情况进行总结并向上级汇报的一种书面文档。它不仅反映了会计人员的工作业绩,还体现了其对会计准则...

    c++期末考试判断题+填空题.pdf

    - 第九题说明要实现动态联编,基类和派生类的函数需有相同的名称、返回类型以及参数,这是正确的。 - 第十四题提到对象数组可以赋初值但不能赋值,这个描述是错误的,对象数组可以进行赋值操作。 6. **构造函数与...

    吉林省白城市2020_2021学年高二数学下学期期末考试试题文

    这篇文档是关于吉林省白城市2020-2021学年高二数学下学期期末考试的试题,主要涵盖单选题、填空题和解答题等多种题型,涉及的知识点广泛,包括概率论、逻辑推理、数列、复数、函数、平面几何、坐标系、线性代数等多...

    华夏会计网会计证继续教育题.doc

    这篇文档包含了华夏会计网会计证继续教育的考试题目,涵盖了会计领域的多个知识点,包括生产类型的区分、费用分类、成本计算方法、库存商品核算、发票管理、支票使用规范、记账凭证的编制以及成本会计的任务和成本...

Global site tag (gtag.js) - Google Analytics