在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中,则调用重写的函数。
"如果一个方法被声明为
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偏移。