当调用一个虚函数时,被执行的代码必须与调用函数对象的动态类型相一致:指向对象的指针或引用的类型是不重要的,编译器是如何高效地提供这种行为呢?大多数编译器是使用virtual table和virtual table pointers(vtbl和vptr)。

一个vtbl通常是一个函数指针数组,在程序中每个类只要声明了虚函数,它就有自己的vtbl,并且类中的vtbl的内容是指向所有该类虚函数实现体的指针,例如:

class Test1{
virtual void A();
virtual void B();
virtual void C();
virtual void D();
};

Test的虚函数表会是这样的:

如果有一个Test2继承了Test1:

class Test2:public Test1{
virtual void C();
virtual void D();//重定义C和D
}

Test2的虚函数表会是这样的:

这个结果表明:如果你有大量的类或者在每个类中有大量的虚函数,你会发现vtbl会yonkers大量的地址空间。

问题:每个类都只需要一个vtbl拷贝,编译器该把它放在哪里?

通常采用启发式算法:要在一个目标文件中生成一个雷的虚函数表,要求改目标文件中包含该类的第一个非内联,非纯虚的函数定义。

同事虚函数表只实现了虚拟函数的一半机制,如果只有这些事没用的,需要一个指向虚函数表的指针来建立类和表的联系,每个生命了虚函数的对象都带有它,它是一个看不见的数据成员,指向该类的虚函数表,这个看不见的数据成员被称为vptr,被编译器加载对象里,位置只有编译器知道,其内存布局可能是这样的:

但是不同的编译器放置它的位置不同,存在继承的情况下,一个对象的vptr经常被数据成员所包围,如果存在多继承,将会更加复杂。

这也就意味着,如果你的数据成员只有4个字节,那么额外的虚函数指针会使得成员数据大小扩大一倍。

考虑这段代码:

void call(Test1 *ptr)
{
ptr->A();
}

通过指针ptr调用虚函数A,编译器在这个调用过程中生成的代码会做如下的事情:

1.通过对象的vptr找到雷的vtbl,这是一个很简单的动作,因为编译器知道在对象内哪能找到vptr(毕竟是由编译器放置的)。这个代价只是一个偏移调整(找到虚函数指针)和一个指针的间接寻址(得到vtbl)。

2.找到对应vtbl内的指向被调用函数的指针,这也是很简单的,因为编译器为每个虚函数在vtbl内分配了一个唯一的索引,这个代价只是在vtbl数组内的一个偏移。

3.调用第二步找到的指针所指向的函数。

上述的调用会变成这样:

(*ptr->vptr[i])(ptr);//ptr被当做this指针传递给函数

在实际运行中,虚函数所需要的代价和内联函数有关,实际上虚函数无法内联。因为内联是“编译期间用被调用的函数体本身来代替函数调用的指令”,但是虚函数是“知道运行时才能知道调用了哪一个函数”。

在多继承中,问题会更复杂,如果没有virtual base class,子类会包含多个虚表以及指向虚表的指针,然而,一半会引入虚基类(为了防止钻石继承),按照《深入探索C++对象模型》中的说法,这样会引入两个问题:

1.每一个对象必须针对其每一个virtual base class背负一个额外的指针,但我们却希望class object有固定的负担,不因为其virtual base class的数目而有变化。

2.由于虚继承串链的加长,导致间接存取层次增加。比如:有三层虚继承,那需要三级间接存取(经由三个virtual base class指针),但我们希望有固定的存取时间。

第一个问题一般有两种解决方法:

Microsoft编译器引入所谓的virtual base class table,每一个class object,如果有一个或者多个virtual base class,就会由编译器安插一个指针,指向virtual base class table,至于自己的virtual base class指针则会放在该表格中,比如:

第二个解决办法就是在虚函数表中放置虚基类的偏移量:

RTTI:运行时类型识别

RTTI能让我们在运行时找到对象和类的有关信息,所以肯定有某个地方存储了这些信息让我们查询,这些信息被存放在type——info的对象中,而RTTI被设为基于虚函数表来实现

因此Test1类的实现可能是这样:

最新文章

  1. 为你带来灵感的 20 个 HTML5/CSS3 模板
  2. 转:C# 使用NLog记录日志
  3. APUE第五章:标准IO库
  4. Android 图片开发内幕系列第一篇
  5. Rational Rose2013安装及破解教程
  6. Life Forms (poj3294 后缀数组求 不小于k个字符串中的最长子串)
  7. EOS 新增的 WebAssembly 解释器,是什么鬼?
  8. excel poi导出demo
  9. jQuery实现简单的五星好评
  10. [C# ASP.NET]如何让IIS Express支持外部(局域网)连接
  11. AOP - C# Fody中的方法和属性拦截
  12. js中 let 与 var 的区别
  13. JS实现复制页面文字弹出消息提醒
  14. Swap HDU - 2819 (有关矩阵的二分匹配)
  15. js跨域请求提示函数未定义的问题
  16. An impassioned circulation of affection(尺取+预处理)
  17. 复制web项目,启动的时候的工程名如何改变
  18. linux grub 使用
  19. node.js---sails项目开发(1)
  20. Sybase数据库:两个特别注意的地方

热门文章

  1. Java登录专题-----创建用户(一)
  2. 教你用canvas打造一个炫酷的碎片切图效果
  3. LcdToos如何实现PX01自动调Flicker及VCOM烧录
  4. Jquery中Trigger()方法
  5. 获取不同机型微信小程序状态栏+导航栏高度
  6. Centos镜像下载
  7. web前端学习之旅笔记01--HTML
  8. Python标准库之 xml.etree.ElementTree
  9. OpenCvSharp的安装和使用
  10. linux如何修改dns