编程C++开发者

在C++中,什么是虚拟表(vtable)?编译器如何实现动态多态,并且与此机制相关的细节是什么?

用 Hintsage AI 助手通过面试

答案。

在C++中,动态多态通过虚拟函数机制实现,使用一个特殊的表——vtable(虚拟表)。对于任何具有至少一个虚拟函数的类,编译器生成vtable:这是一个指向类的虚拟函数的指针数组。每个这样的类的对象存储一个隐藏指针指向vtable——所谓的vptr(虚拟指针)。

在创建具有虚拟函数的类的对象时,vptr被初始化为指向对象所属类的vtable。当调用虚拟函数时,调用不是直接进行的,而是通过存储在vtable中的地址进行,选择正确的实现,这取决于对象的实际类型(即使是通过基类类型进行访问)。

示例:

class Base { public: virtual void foo() { std::cout << "Base::foo() "; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo() "; } }; void call_foo(Base* obj) { obj->foo(); // 通过vtable调用 }

调用foo将是对vtable的访问:如果在Derived中,则调用重写的函数。

细节:

  • 如果类不包含虚拟函数,则不添加vtable和vptr。
  • 虚拟方法不会变为内联,通常会损失性能。
  • 增加了类实例的大小(vptr的大小——通常为4/8字节)。
  • 虚拟调用比直接调用慢。

有陷阱的问题。

"如果一个方法被声明为virtual,但在派生类中没有重写,它是否是虚拟的?在这种情况下通过vtable的调用会工作吗?"

答案:是的,如果在基类中将函数声明为virtual,即使未在基类中重写,它仍然对所有子类保持虚拟特性。无论如何,通过基类指针的调用都会通过vtable进行。如果方法未重写——将调用基类中的版本。

示例:

struct Base { virtual void foo() { std::cout << "B "; } }; struct Derived : Base { }; Base* obj = new Derived(); obj->foo(); // 输出"B",但通过vtable调用!

由于对主题细节不了解而导致的真实错误示例。


故事

在一个大型项目中,忘记将析构函数在基类中声明为虚拟。这导致了内存泄漏——通过基类指针删除时,只调用基类的析构函数,而不是派生类。


故事

在类中声明了一个虚拟方法,由于错误在子类中被隐藏(不是override,而是具有不同的签名/参数名称)。通过基类的调用没有调用到正确的函数,导致不正确的工作。


故事

在多重继承后更新vtable导致错误:在使用dynamic_cast时方法的错误调用,因为没有考虑复杂类层次中的vptr偏移。