转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531
一、虚函数的工作原理
虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式。vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。
虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。
虚函数需要注意的大概就是这些个地方了,之前在More effective C++上好像也有见过,不过这次在Visual C++权威剖析这本书中有了更直白的认识,这本书名字很牛逼,看看内容也就那么回事,感觉名不副实,不过说起来也是有其独到之处的,否则也没必要出这种书了。
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类 中,编译器秘密地置入一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。
通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置V TA B L E、初始化V P T R、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。
[cpp] view plain copy

    #include<iostream>  using namespace std;  class A  {  public:  virtual void fun1()  {  cout << "A::fun1()" << endl;  }  virtual void fun2()  {  cout << "A::fun2()" << endl;  }  };  class B : public A  {  public:  void fun1()  {  cout << "B::fun1()" << endl;  }  void fun2()  {  cout << "B::fun2()" << endl;  }  };  int main()  {  A *pa = new B;  pa->fun1();  delete pa;  system("pause");   return 0;  }  

毫无疑问,调用了B::fun1(),但是B::fun1()不是像普通函数那样直接找到函数地址而执行的。真正的执行方式是:首先取出pa指针所指向的对象的vptr的值,这个值就是vtbl的地址,由于调用的函数B::fun1()是第一个虚函数,所以取出vtbl第一个表项里的值,这个值就是B::fun1()的地址了,最后调用这个函数。因此只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务,多态就是这样实现的。
而对于class A和class B来说,他们的vptr指针存放在何处?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。
虚拟函数使用的缺点
  优点讲了一大堆,现在谈一下缺点,虚函数最主要的缺点是执行效率较低,看一看虚拟函数引发的多态性的实现过程,你就能体会到其中的原因,另外就是由于要携带额外的信息(VPTR),所以导致类多占的内存空间也会比较大,对象也是一样的。
含有虚函数的对象在内存中的结构如下:
[cpp] view plain copy

    class A  {  private:  int a;  int b;  public:  virtual void fun0()  {  cout<<"A::fun0"<<endl;  }  };  

1、直接继承
那我们来看看编译器是怎么建立VPTR指向的这个虚函数表的,先看下面两个类:
[cpp] view plain copy

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

两个类的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,对象就”知道”它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。
没有虚函数类对象的大小正好是数据成员的大小,包含有一个或者多个虚函数的类对象编译器向里面插入了一个VPTR指针(void ),指向一个存放函数地址的表就是我们上面说的VTABLE,这些都是编译器为我们做的我们完全可以不关心这些。所以有虚函数的类对象的大小是数据成员的大小加上一个VPTR指针(void )的大小。

总结一下VPTR 和 VTABLE 和类对象的关系:
每一个具有虚函数的类都有一个虚函数表VTABLE,里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表VTABLE是这个类的所有对象所共有的,也就是说无论用户声明了多少个类对象,但是这个VTABLE虚函数表只有一个。
在每个具有虚函数的类的对象里面都有一个VPTR虚函数指针,这个指针指向VTABLE的首地址,每个类的对象都有这么一种指针。
2、虚继承
这个是比较不好理解的,对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表。另外,派生类虚继承父类时,首先要通过加入一个虚指针来指向父类,因此有可能会有两个虚指针。

C++中虚函数工作原理相关推荐

  1. C++中虚函数工作原理和(虚)继承类的内存占用大小计算

    转载请标明出处,原文地址:http://blog.csdn.net/hackbuteer1/article/details/7883531 一.虚函数的工作原理       虚函数的实现要求对象携带额 ...

  2. 关于SOCKET中send和recv函数工作原理总结

    send函数工作原理: send函数只负责将数据提交给网络协议层. 当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SO ...

  3. socket api中send()和recv()函数工作原理与要点

    send()和recv()函数是网络编程中经常使用到的函数,下面详细的比较两者的不同之处 send函数工作原理: send函数只负责将数据提交给协议层. 当调用该函数时,send先比较待发送数据的长度 ...

  4. C++中虚函数、虚指针和虚表详解

    关于虚函数的背景知识 用virtual关键字申明的函数叫做虚函数,虚函数肯定是类的成员函数. 存在虚函数的类都有一个一维的虚函数表叫做虚表.每一个类的对象都有一个指向虚表开始的虚指针.虚表是和类对应的 ...

  5. 【转】c++虚函数实现原理

    [转]c++虚函数实现原理 原文链接:https://blog.csdn.net/neiloid/article/details/6934135 C++中的虚函数的作用主要是实现了多态的机制.关于多态 ...

  6. 浅析μC/OS-II OSTimeDly()函数和OSTimeTick()函数工作原理

    浅析μC / OS - II v2 . 85内核OSTimeDly ( ) 函数工作原理 文章来源 : http : //gliethttp.cublog.cn[转载请声明出处] //-------- ...

  7. api中重载函数的原理_小心重载API方法

    api中重载函数的原理 重载方法是API设计中的一个重要概念,尤其是当您的API是流利的API或DSL( 特定于域的语言 )时. 对于jOOQ就是这种情况,在这种情况下,您经常想使用与完全相同的方法名 ...

  8. linux 内核态 cos函数,浅析μCOS/II v2.85内核OSMboxPend()和OSMboxPost()函数工作原理

    浅析μCOS/II v2.85内核OSMboxPend()和OSMboxPost()函数工作原理 文章来源:http://gliethttp.cublog.cn[转载请声明出处] //-------- ...

  9. 深入浅出地理解STM32中的定时器工作原理

    深入浅出地理解STM32中的定时器工作原理 一.如何实现延时 1 纯硬件电路 2 纯软件编程 3 可编程定时/计数器 二.可编程定时/计数器有哪些功能? 三.STM32F103 的定时器有哪些 1 基 ...

最新文章

  1. 经过五年测试 维基利用HTML5迈入视频时代
  2. 使用选择器语法来查找元素 - 你想使用类似于CSS或jQuery的语法来查找和操作元素...
  3. 使用区分优先级的负载分流法确保Netflix的可靠性
  4. 打破牢笼,展望更高层次的世界
  5. zookeeper无法启动的原因定位
  6. 一条SQL语句求前面记录的平均值
  7. MFC加入mysql后编译成功,在自己电脑上成功运行,当打包发送到其他电脑上报错, 缺少libcrypto-1_1-x64.dll以及缺少libssl-1_1-x64.dll问题解决方案,完美亲测
  8. 图灵工业机器人说明书_从2020世界人工智能大会,看工业机器人领域领军企业布局...
  9. 数据结构中的校园导航系统---c语言
  10. wav格式怎么转换成mp3
  11. ios百度地图开发之路径规划
  12. scala with cats 之 Contravariant Functors and Invariant Functors
  13. iOS 开发之使用 Facebook POP
  14. 基于Spring Boot 2.5.1 微服务框架发布(Eurynome Cloud )
  15. 怎么用计算机给u盘加密文件,电脑如何使用bitlocker功能给u盘加密
  16. 关于微信防撤回(文本、图片、语音、视频、名片等...)的Python学习教程
  17. 推荐三款换装变脸软件给你
  18. 长沙市六中学生寒假社会实践活动在湖南智慧教育装备展示体验中心开展
  19. HIVE自定义UDF函数-经纬度转换成省市地址
  20. 第六届未来网络发展大会,即将开幕!

热门文章

  1. wordpress最佳架构_25个旅行博客的最佳WordPress主题(2020)
  2. c语言reverse怎么起作用,reverse的用法
  3. 智能计算系统2 bangc算子开发的demo (CPU和MLU270的异构编程流程)
  4. PD3.0详解 电源规则,全解!!!全解!!!
  5. Stable Diffusion 图片生成AI模型 Windows Mac部署指南
  6. CloudStack 4.17 安装部署
  7. java实现坑爹大冒险游戏(附完整源码)
  8. 2D随机地图算法整理
  9. 硬核HashMap源码分析,HashMap文章中的圣经
  10. 老王和他的IT界朋友们 ---- 交流渠道