面试题答案
一键面试虚函数底层实现原理
- 虚函数表(vtable)结构:
- 每个含有虚函数的类都有一个虚函数表。虚函数表是一个函数指针数组,数组中的每个元素指向类中的一个虚函数的实现。
- 对于多重继承的派生类,它会有多个虚函数表,每个虚函数表对应一个含有虚函数的基类。例如,如果一个派生类
Derived
从两个含有虚函数的基类Base1
和Base2
继承,那么Derived
会有两个虚函数表,分别对应Base1
和Base2
。 - 虚函数表中的函数指针顺序与类定义中虚函数声明的顺序一致。如果派生类重写了某个虚函数,那么虚函数表中对应位置的函数指针会指向派生类中重写的函数实现。
- 虚函数表指针(vptr)设置:
- 每个包含虚函数的对象都含有一个虚函数表指针。在对象构造时,虚函数表指针会被设置为指向该对象所属类对应的虚函数表。
- 对于多重继承的对象,在对象布局中会有多个虚函数表指针,分别对应不同的基类虚函数表。例如,对于从
Base1
和Base2
继承的Derived
对象,其内存布局可能是先存放对应Base1
的虚函数表指针,然后是Base1
的其他成员,接着是对应Base2
的虚函数表指针,再是Base2
的其他成员,最后是Derived
自己的成员。
- 函数调用过程:
- 当通过对象指针或引用调用虚函数时,首先会根据对象的虚函数表指针找到对应的虚函数表。
- 然后,根据虚函数在虚函数表中的索引(即虚函数在类定义中声明的顺序),找到虚函数表中对应的函数指针。
- 最后,通过该函数指针调用实际的虚函数实现。例如,假设有一个
Derived
类对象的指针pDerived
,调用pDerived->VirtualFunction()
,会先找到pDerived
对应的虚函数表(根据对象类型和继承关系确定是哪个虚函数表),再通过虚函数索引找到VirtualFunction
对应的函数指针并调用。
菱形继承问题与虚函数实现的关联
- 菱形继承问题:
- 菱形继承是指一个派生类从多个直接基类继承,而这些直接基类又继承自同一个间接基类。例如,有
A
类,B
和C
类都继承自A
,D
类同时继承自B
和C
。这样D
类中会有A
类成员的两份拷贝,导致数据冗余和访问歧义。 - 当虚函数涉及菱形继承时,由于可能存在多个虚函数表指针指向不同路径下的虚函数表,可能会导致虚函数调用的混乱。例如,在
D
类对象中,如果不同路径的虚函数表对虚函数的索引不一致,就会出现错误的虚函数调用。
- 菱形继承是指一个派生类从多个直接基类继承,而这些直接基类又继承自同一个间接基类。例如,有
- 关联分析:
- 虚函数表的结构和虚函数表指针的设置在菱形继承中变得更加复杂。因为不同路径下的虚函数表需要协调,以保证正确的虚函数调用。如果处理不当,会出现虚函数调用错误,同时也会加重内存管理的负担,因为可能存在重复的虚函数表指针和虚函数表。
解决方法
- 虚继承:
- C++提供虚继承来解决菱形继承问题。当使用虚继承时,从虚基类继承的成员在派生类中只会有一份拷贝。例如,在上述菱形继承例子中,
B
和C
类可以虚继承自A
类(class B : virtual public A
和class C : virtual public A
)。 - 对于虚函数,虚继承保证了虚函数表的一致性。在虚继承体系下,编译器会对虚函数表进行特殊处理,使得不同路径下的虚函数表对虚函数的索引保持一致,从而保证正确的虚函数调用。
- 虚继承通过引入虚基类指针来定位虚基类子对象,在对象构造和析构时,编译器会进行额外的操作来确保虚基类子对象的正确初始化和销毁,同时保证虚函数表的正确设置和使用。
- C++提供虚继承来解决菱形继承问题。当使用虚继承时,从虚基类继承的成员在派生类中只会有一份拷贝。例如,在上述菱形继承例子中,