虚函数实现原理:
一个含有虚函数的类都有至少一个虚函数表,里面存放着该类所有虚函数的指针。
类的所有对象都共享类的虚函数表,这些对象都有一个或多个虚函数表指针来指向一个或多个虚函数表。
子类继承父类时也会继承父类的虚函数表,但父类的虚函数表和子类继承过来的虚函数表是两个独立的表。
当子类重写父类中虚函数的时候,会用重写后函数的指针代替继承过来的虚函数表中的原函数指针。
当使用父类类型的指针指向一个父类或子类对象,并调用其虚函数,会在程序运行时根据指向的对象是父类还是子类对象来决定调用的是父类中的虚函数还是子类重写的虚函数,称为动态多态。具体实现原理:当用父类指针指向子类对象时会进行指针转换,使得父类指针指向子类对象中的父类部分,通过指针调用指向对象的虚函数时,会通过指向对象中起始位置的虚函数表指针找到虚函数表,经过一定的偏移量找到对应的虚函数指针。
单继承内存布局:
多继承:
当一个类继承多个类,且多个基类都有虚函数表时,子类将有多个虚函数表,子类对象中将包含多个虚函数表的指针vptr。
多继承内存布局:
派生类与基类B共用一个虚函数表,称B为主基类。
派生类自己的虚函数都放在从主基类继承过来的虚函数表上,派生类自己的成员对象都放在主基类继承过来的成员对象后。
当基类B和C有同名函数时,在派生类中不会发生覆盖,会根据指针类型决定执行B或C的同名函数。
但是如果派生类重写了上述同名函数,主基类B的虚函数表中同名函数指针被重写函数指针覆盖,基类C的虚函数表中同名函数指针被覆盖为指向主基类B的虚函数表中被覆盖位置的指针,即覆盖后为被重写函数的间接指针。
这是因为只有第一个基类对象的this指针才与派生对象的this指针相同,其他基类对象的this指针相对于派生类对象的this指针发生了偏移,所以需要由代码段non-virtual thunk to Derived::f()将this指针转换为派生类this的指针,之后才跳转到真正的Derived::f()函数。
虚函数的调用过程与前面描述基本类似,区别在于基类指针指向的位置可能不是派生类对象的起始位置:
指向虚函数表的指针存在于对象实例中最前面的位置,保证取到的虚函数表有最高的性能。
虚函数表布局:
实际指向虚函数表的指针指向的并非虚函数表的首地址,虚函数表应该还包括虚函数表指针指向的地址的前两个地址空间(64位系统,也就是16个字节)。
offset_to_top:虚函数表指针相对于对象的内存起始位置的偏移量。因为在多继承中,各个基类部分在派生类中的位置不同,基类对象转换为派生类时,需要知道派生类对象起始位置到当前基类对象起始位置的偏移量,让this指针加上offset_to_top这个偏移量得到派生类对象的起始地址。offset_to_top只能是0或负数,因为堆是向上生长,越往下地址越大,所以基类对象起始地址要减小到派生类对象起始地址。
typrinfo for base:里面是内容是一个指针,指向该基类的运行时信息,为了支持RTTI。
C++有两种方式实现多态,即重载和覆盖:
1.重载:指允许存在多个同名函数,而这些函数的参数表不同(参数个数或类型不同)。静态多态,编译时确定。
2.覆盖:指子类重新定义父类虚函数。用父类指针指向父类或子类对象,程序运行时根据指向的对象是父类还是子类对象来决定调用的是父类中的虚函数还是子类中重写的虚函数。动态多态,运行时确定。
参考:
https://blog.csdn.net/Xiejingfa/article/details/50454819