当调用一个虚函数时,被执行的代码必须与调用函数对象的动态类型相一致:指向对象的指针或引用的类型是不重要的,编译器是如何高效地提供这种行为呢?大多数编译器是使用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类的实现可能是这样:

深入理解C++虚函数底层机制和RTTI运行时类型识别的更多相关文章

  1. MFC六大核心机制之二:运行时类型识别(RTTI)

    上一节讲的是MFC六大核心机制之一:MFC程序的初始化,本节继续讲解MFC六大核心机制之二:运行时类型识别(RTTI). typeid运算子 运行时类型识别(RTTI)即是程序执行过程中知道某个对象属 ...

  2. 深入浅出理解c++虚函数

    深入浅出理解c++虚函数   记得几个月前看过C++虚函数的问题,当时其实就看懂了,最近笔试中遇到了虚函数竟然不太确定,所以还是理解的不深刻,所以想通过这篇文章来巩固下. 装逼一刻: 最近,本人思想发 ...

  3. 基类中定义的虚函数在派生类中重新定义时,其函数原型,包括返回类型、函数名、参数个数、参数类型及参数的先后顺序,都必须与基类中的原型完全相同 but------> 可以返回派生类对象的引用或指针

      您查询的关键词是:c++primer习题15.25 以下是该网页在北京时间 2016年07月15日 02:57:08 的快照: 如果打开速度慢,可以尝试快速版:如果想更新或删除快照,可以投诉快照. ...

  4. Java泛型函数的运行时类型检查的问题

    在一个数据持久化处理中定义了数据保存和读取的 泛型函数的,但是在运行时出现类型转换错误,类型不匹配,出错的位置不是load方法,而是在调用load方法之后,得到了列表数据,对列表数据进行使用时出现的. ...

  5. 从实用主义深入理解c++虚函数

    记得几个月前看过C++虚函数的问题,当时其实就看懂了,最近笔试中遇到了虚函数竟然不太确定,所以还是理解的不深刻,所以想通过这篇文章来巩固下. 装逼一刻: 最近,本人思想发生了巨大的转变,在大学的时候由 ...

  6. c++虚函数实现机制(转)

    前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛 ...

  7. C++ //纯虚函数和抽象类 // 语法 virtual 返回值类型 函数名 (参数列表)=0 //当类中有了纯虚函数 这个类也称为抽象类

    1 //纯虚函数和抽象类 2 // 语法 virtual 返回值类型 函数名 (参数列表)=0 3 //当类中有了纯虚函数 这个类也称为抽象类 4 5 6 #include <iostream& ...

  8. 关于Class对象、类加载机制、虚拟机运行时的内存布局的全面解析和推测

    简介: 本文是对Java的类加载机制,Class对象,反射原理等相关概念的理解.验证和Java虚拟机中内存布局的一些推测.本文重点讲述了如何理解Class对象以及Class对象的作用. 欢迎探讨,如有 ...

  9. 读书笔记:深入理解java虚拟机(一)虚拟机的运行时的数据区域

    最近在看深入了解java虚拟机第一版(周志明著),特此写读书笔记,整理其中重要的东西和自己的理解. ”java与c++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却 ...

  10. 深入理解JVM虚拟机:(一)Java运行时数据区域

    概述 JVM是Java语言的精髓所在,因为它Java语言实现了跨平台运行,以及自动内存管理机制等,本文将从概念上介绍JVM内存的各个区域,说明个区域的作用. JVM运行时数据区模型 Java虚拟机在执 ...

随机推荐

  1. SSM框架整合图书管理项目

    SSM框架整合 1.建立简单的maven项目 2.导入依赖 <?xml version="1.0" encoding="UTF-8"?> <p ...

  2. 齐博x1 小程序与公众号长期永久订阅消息的申请方法

    要给用户发送消息提醒的话,需要申请订阅消息.订阅消息分一次性订阅与长期永久性订阅.一次性订阅没有实际意义,用户订阅一次就只能发送一次.这里主要是指导大家如何申请永久长期订阅功能.对于公众号而言,大家先 ...

  3. 齐博x1一段不错的小js提高一点点阅读体验 计算本文阅读所需的时长

    如图所示很多比较大的站点都有这样的一个小玩意 就是本文有多少字 阅读需要多少时间. 一段小小的js就可以实现,当然了php也可以功能太小了不值得做钩子或者插件自己需要的话再模板加一下吧. <sc ...

  4. eDP接口简介

    1. eDP背景介绍   随着显示分辨率的越来越高,传统的VGA.DVI等接口逐渐不能满足人们的视觉需求. 随后就产生了以HDMI.DisplayPort为代表的新型数字接口,外部接口方面HDMI占据 ...

  5. DQL语句排序与分组

    DQL语句排序与分组 一.DQL-排序 排序是计算机内经常进行的一种操作,其目的是将一组"无序"的记录序列调整为"有序"的记录序列.分内部排序和外部排序,若整个 ...

  6. 河北首家城商行传统核心业务国产化,TDSQL突破三“最”为秦皇岛银行保驾护航

    11 月 1 日,秦皇岛银行新一代分布式核心系统成功投产并稳定安全运行超过三个月,标志着秦皇岛银行数字化转型应用和服务水平登上了一个新台阶. 这是秦皇岛银行有史以来规模最大.范围最广.难度最高的一次系 ...

  7. 这是不是你想要了解SQL的艺术,基础语法等等

    一.基础sql语句: 模块定义 基础语句 基础功能 数据定义 create table 创建数据库表 drop table 删除数据表 alter table 修改表结构 create view 创建 ...

  8. <五>关于类的各类成员

    类的各种成员-> 成员方法 & 成员变量 普通的成员方法=>编译器会添加一个this形参变量 1:属于类的作用域 2:调用该方法时,需要依赖一个对象,而且常对象不能调 3:可以任意 ...

  9. linux子网掩码修改记录

    1.输入密码进入linux,并且进入root2.输入ifconfig.返回网卡信息,释:其中eno1为当前以太网名称.Inet IP/子网掩码位置数 Bcast广播地址 或者mask子网掩码3.修改子 ...

  10. i春秋123

    打开是个普普通通的登录窗口,下尝试根据提示12341234进行输入,发现不正确...可能1234是指步骤,然后查看源码 发现了绿色的提示信息,我们就根据提示试试打开user.php 打开是白板网页,源 ...