当前位置: 首页 > 编程笔记 >

浅谈C++中虚函数实现原理揭秘

叶华皓
2023-03-14
本文向大家介绍浅谈C++中虚函数实现原理揭秘,包括了浅谈C++中虚函数实现原理揭秘的使用技巧和注意事项,需要的朋友参考一下

编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟。     

编译器对每个包含虚函数的类创建一个表(称为V TA B L E)。在V TA B L E中,编译器放置特定类的虚函数地址。在每个带有虚函数的类 中,编译器秘密地置一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E。通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置V TA B L E、初始化V P T R、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。利用虚函数, 这个对象的合适的函数就能被调用,哪怕在编译器还不知道这个对象的特定类型的情况下。(《C++编程思想》)

在任何类中不存在显示的类型信息,可对象中必须存放类信息,否则类型不可能在运行时建立。那这个类信息是什么呢?

我们来看下面几个类:

class no_virtual

{
public:
  void fun1() const{}
  int fun2() const { return a; }
private:
  int a;
} class one_virtual
{
public:
  virtual void fun1() const{}
  int fun2() const { return a; }
private:
  int a;
} class two_virtual
{
public:
  virtual void fun1() const{}
  virtual int fun2() const { return a; }
private:
  int a;
} 

以上三个类中:

no_virtual没有虚函数,sizeof(no_virtual)=4,

类no_virtual的长度就是其成员变量整型a的长度;

one_virtual有一个虚函数,sizeof(one_virtual)=8;

two_virtual 有两个虚函数,sizeof(two_virtual)=8;

有一个虚函数和两个虚函数的类的长度没有区别,其实它们的长度就是no_virtual的 长度加一个void指针的长度,它反映出,如果有一个或多个虚函数,编译器在这个结构中插入一个指针( V P T R)。在one_virtual 和 two_virtual之间没有区别。这是因为V P T R指向一个存放地址的表,只需要一个指针,因为所有虚函数地址都包含在这个表中。 这个VPTR就可以看作类的类型信息。

那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的。先看下面两个类:

class base
{
public:
  void bfun(){}
  virtual void vfun1(){}
  virtual int vfun2(){}
private:
  int a;
} class derived : public base
{
public:
  void dfun(){}
  virtual void vfun1(){}
  virtual int vfun3(){}
private:
  int b;
} 

两个类VPTR指向的虚函数表(VTABLE)分别如下:

base类

 

      ——————
VPTR——> |&base::vfun1 |
      ——————
     |&base::vfun2 |
     ——————

derived类

      ———————
VPTR——> |&derived::vfun1 |
      ———————
     |&base::vfun2 |
     ———————
     |&derived::vfun3 |
     ———————

每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个VTABLE,如上图所示。在这个表中,编译器放置了在这个类 中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义,编译器就使用基类 的这个虚函数地址。(在derived的VTABLE中,vfun2的入口就是这种情况。)然后编译器在这个类中放置VPTR。当使用简单继承时,对于每 个对象只有一个VPTR。VPTR必须被初始化为指向相应的VTABLE,这在构造函数中发生。

一旦VPTR被初始化为指向相应的VTABLE,对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。       

个人总结如下:

1、从包含虚函数的类派生一个类时,编译器就为该类创建一个VTABLE。其每一个表项是该类的虚函数地址。

2、在定义该派生类对象时,先调用其基类的构造函数,然后再初始化VPTR,最后再调用派生类的构造函数( 从二进制的视野来看,所谓基类子类是一个大结构体,其中this指针开头的四个字节存放虚函数表头指针。执行子类的构造函数的时候,首先调用基类构造函数,this指针作为html" target="_blank">参数,在基类构造函数中填入基类的vptr,然后回到子类的构造函数,填入子类的vptr,覆盖基类填入的vptr。如此以来完成vptr的初始化。 )

3、在实现动态绑定时,不能直接采用类对象,而一定要采用指针或者引用。因为采用类对象传值方式,有临时基类对象的产生,而采用指针,则是通过指针来访问外部的派生类对象的VPTR来达到访问派生类虚函数的结果。       

VPTR 常常位于对象的开头,编译器能很容易地取到VPTR的值,从而确定VTABLE的位置。VPTR总指向VTABLE的开始地址,所有基类和它的子类的虚函 数地址(子类自己定义的虚函数除外)在VTABLE中存储的位置总是相同的,如上面base类和derived类的VTABLE中vfun1和vfun2 的地址总是按相同的顺序存储。编译器知道vfun1位于VPTR处,vfun2位于VPTR+1处,因此在用基类指针调用虚函数时,编译器首先获取指针指 向对象的类型信息(VPTR),然后就去调用虚函数。如一个base类指针pBase指向了一个derived对象,那pBase->vfun2 ()被编译器翻译为 VPTR+1 的调用,因为虚函数vfun2的地址在VTABLE中位于索引为1的位置上。同理,pBase->vfun3 ()被编译器翻译为 VPTR+2的调用。这就是所谓的晚绑定。

以上这篇浅谈C++中虚函数实现原理揭秘就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持小牛知识库。

 类似资料:
  • 本文向大家介绍浅谈MyBatis通用Mapper实现原理,包括了浅谈MyBatis通用Mapper实现原理的使用技巧和注意事项,需要的朋友参考一下 本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。 基本原理 通用 Mapper 提供了一些通用的方法,这些通用方法是以接口的形式提供的,例如。 接口和方法都使用了泛型,使用该通用方法的接口需要指定泛型的类型。通过 Jav

  • 本文向大家介绍浅谈vuejs实现数据驱动视图原理,包括了浅谈vuejs实现数据驱动视图原理的使用技巧和注意事项,需要的朋友参考一下 什么是数据驱动 数据驱动是vuejs最大的特点。在vuejs中,所谓的数据驱动就是当数据发生变化的时候,用户界面发生相应的变化,开发者不需要手动的去修改dom。 比如说我们点击一个button,需要元素的文本进行是和否的切换。在jquery刀耕火种的年代中,对于页面的

  • 本文向大家介绍浅谈vue中数据双向绑定的实现原理,包括了浅谈vue中数据双向绑定的实现原理的使用技巧和注意事项,需要的朋友参考一下 vue中最常见的属v-model这个数据双向绑定了,很好奇它是如何实现的呢?尝试着用原生的JS去实现一下。 首先大致学习了解下Object.defineProperty()这个东东吧! 以上总结了对象的defineProperty四个属性:configurable,e

  • 本文向大家介绍浅谈express 中间件机制及实现原理,包括了浅谈express 中间件机制及实现原理的使用技巧和注意事项,需要的朋友参考一下 简介 中间件机制可以让我们在一个给定的流程中添加一个处理步骤,从而对这个流程的输入或者输出产生影响,或者产生一些中作用、状态,或者拦截这个流程。中间件机制和tomcat的过滤器类似,这两者都属于责任链模式的具体实现。 express 中间件使用案例 模拟中

  • 本文向大家介绍c++中虚函数的实现详解,包括了c++中虚函数的实现详解的使用技巧和注意事项,需要的朋友参考一下 前言 c++ 分为编译时多态和运行时多态。运行时多态依赖于虚函数,大部分人或许听说过虚函数是由虚函数表+虚函数指针实现的,但,真的是这样吗?虽然 c++ 规范有着复杂的语言细节,但底层实现机制却任由编译器厂商想象。(没准某种特殊的处理器电路结构原生支持虚函数,没准这个处理器压根不是冯纽曼

  • 本文向大家介绍浅谈PHP Cookie处理函数,包括了浅谈PHP Cookie处理函数的使用技巧和注意事项,需要的朋友参考一下 (o゜▽゜)o☆[BINGO!] ok,我们先看看cookie是什么东东? cookie是服务器留在客户端的用于识别用户或者存储一些数据的小文件(注意,session存储在服务器端,这是两者的区别之一)。我们平时登录某门户时,会有选项“是否保存登录”或者“下次自动登录”,