技术交流QQ群:1027579432,欢迎你的加入!

1.概述

  • 每一个含有虚函数(无论是其本身就含有的,还是从基类继承过来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针。如下图所示

    虚函数表.png

    其中:
    • B的虚函数表中存放着B::foo和B::bar两个函数指针
    • D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz。

2.虚函数表的构造过程

  • 从编译器的角度来说,B的虚函数表很好构造,D的虚函数表构造过程相对复杂,下面给出了构造D的虚函数表的一种方式,该过程是由编译器完成的,即虚函数替换过程发生在编译时:


    D的虚函数表构造过程.png

3.虚函数的调用过程

  • 以下面的程序为例:


    虚函数的调用过程.png
  • 编译器只知道pb是B*类型的指针,并不知道它指向的具体对象类型,pb可能指向的是B的对象,也有可能指向的是D的对象。但对于pb->bar(),编译时能确定的是:此处->的另一个参数是B::bar(因为pb是B*类型的,编译器认为bar是B::bar),而B::bar和D::bar在各自虚函数表中的偏移位置是相等的。无论pb指向哪种类型的对象,只要能够确定被调函数在虚函数表的偏移值,等到运行时能够确定具体类型,并能找到相应的vptr,这样就能找出真正应该调用的函数。由于虚函数指针中的ptr部分为虚函数表中的偏移值(以字节为单位)+1,所以B::bar是一个虚函数指针,它的ptr部分内容是9,它在B的虚函数表中的偏移值为8。当程序执行到pb->bar()时,已经能够判断pb指向的具体类型了:
    • a.如果pb指向B的对象,可以获取到B对象的vptr,加上偏移值8(char(*)vptr + 8),可以找到B::bar
    • b.如果pb指向的是D的对象,可以获取D的对象vptr,加上偏移值8(char(*)vptr + 8),可以找到D::bar

3.多重继承

  • 当一个类继承多个类,且多个基类都有虚函数时,子类对象中将包含多个虚函数表中的指针(即多个vptr),如下所示:


    多重继承.png
  • 其中:D的自身虚函数与B基类共用了同一个虚函数表,因此称B为D的主基类。虚函数替换过程与前面描述类似,只是多了一个虚函数表,多了一次拷贝和替换的过程。虚函数的调用过程,与前面基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置,如下面的程序所示:


    多重继承的函数调用.png

4.博客原文