面试题答案
一键面试虚指针的工作原理
- 概念:虚指针(vptr)是一个指向虚函数表(vtable)的指针。在含有虚函数的类对象中,虚指针通常位于对象内存布局的开头。
- 赋值时机:当创建一个派生类对象时,编译器会在构造函数中初始化虚指针,使其指向对应的虚函数表。对于多重继承的派生类,可能会有多个虚指针,分别对应不同基类的虚函数表。
内存布局
- 单一基类情况:对于单一基类且含有虚函数的情况,对象内存布局一般为:vptr(指向该基类的虚函数表) + 其他成员变量。
- 多重继承情况:
- 派生类从多个含有虚函数的基类继承时,内存布局会相对复杂。通常每个基类的虚指针(如果有)会按照继承顺序排列在对象内存布局的前面,然后是派生类自身的成员变量。
- 例如,假设有
class Base1 { virtual void func1(); };
,class Base2 { virtual void func2(); }
,class Derived : public Base1, public Base2 { /*... */ };
,那么Derived
对象的内存布局可能是:Base1的vptr
+Base2的vptr
+Derived类成员变量
。 - 每个虚函数表(vtable)中存放着对应基类及其派生类中虚函数的地址。如果派生类重写了基类的虚函数,虚函数表中对应的函数地址会被替换为派生类中重写函数的地址。
编译器调用正确虚函数的方式
- 通过虚指针找到虚函数表:当通过对象指针或引用调用虚函数时,编译器首先根据对象的类型确定虚指针的位置(因为虚指针位置在对象布局中是固定的)。然后通过虚指针找到对应的虚函数表。
- 在虚函数表中查找函数地址:虚函数表中每个条目对应一个虚函数的地址。编译器根据虚函数在虚函数表中的索引(这个索引是在编译期确定的,基于虚函数声明的顺序)找到具体虚函数的地址。
- 调用函数:找到虚函数的地址后,编译器生成代码来调用该函数,从而实现动态绑定,确保调用的是运行时对象实际类型所对应的虚函数版本。