MST

星途 面试题库

面试题:C++中虚函数表的结构及访问方式

请简述C++虚函数表的基本结构。假设我们有一个包含虚函数的类A,以及它的派生类B,如何通过对象指针访问虚函数表,并解释访问过程中涉及到的原理。
28.8万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

1. C++虚函数表的基本结构

  • 虚函数表是一个由函数指针组成的数组。当一个类中声明了虚函数时,编译器会为该类生成一个虚函数表。
  • 表中的每一个元素都是一个指向虚函数的指针。虚函数表的指针(vptr)会被编译器悄悄地插入到类的对象中,通常位于对象内存布局的开始位置。
  • 对于派生类,如果它没有重写基类的虚函数,虚函数表中相应位置的指针会指向基类的虚函数实现;如果派生类重写了基类的虚函数,虚函数表中对应位置的指针会指向派生类自己的虚函数实现。

2. 通过对象指针访问虚函数表及原理

假设类A包含虚函数,类B继承自类A

class A {
public:
    virtual void func1() {}
    virtual void func2() {}
};

class B : public A {
public:
    void func1() override {}
};
  • 访问虚函数表
    • 在C++中,可以通过对象指针访问虚函数表。由于虚函数表指针(vptr)通常位于对象内存布局的开始位置,所以可以通过以下方式获取虚函数表指针(这里通过reinterpret_cast进行指针类型转换,这种方式比较底层且不安全,实际应用中不建议直接这样操作,但能说明原理):
A* aPtr = new B();
typedef void (*FuncPtr)();
FuncPtr* vtable = reinterpret_cast<FuncPtr*>(*(reinterpret_cast<size_t*>(aPtr)));
  • 上述代码中,首先通过reinterpret_cast<size_t*>(aPtr)aPtr转换为size_t*类型,这样就可以获取对象开始位置的值(即vptr),然后通过*(reinterpret_cast<size_t*>(aPtr))获取vptr的值,再将这个值转换为FuncPtr*类型,即虚函数表指针。
  • 原理
    • 动态绑定:当通过对象指针调用虚函数时,实际调用的函数是在运行时根据对象的实际类型确定的。这是因为对象中包含虚函数表指针,通过这个指针可以找到对应的虚函数表。
    • 虚函数表查找:在运行时,程序根据对象指针找到对象内存布局中的虚函数表指针(vptr),然后通过虚函数表指针找到虚函数表。虚函数表中每个函数指针的位置是固定的,根据虚函数在类中声明的顺序确定。例如,第一个声明的虚函数对应的函数指针在虚函数表的第一个位置,第二个声明的虚函数对应的函数指针在虚函数表的第二个位置,以此类推。所以,根据虚函数在表中的位置,可以找到要调用的虚函数的实际地址并进行调用。在上述例子中,B类重写了func1,所以B类对象的虚函数表中func1位置的指针指向B::func1,而func2位置的指针指向A::func2(因为B类没有重写func2)。当通过aPtr(实际指向B类对象)调用func1时,就会根据虚函数表找到B::func1并调用。