真实面经题目 · 原创解析
C++ 虚函数是什么?
考察 C++ 运行时多态的实现和边界,重点是 virtual、vptr/vtable、动态绑定、虚析构和构造析构阶段规则。
真实面经题目 · 原创解析
考察 C++ 运行时多态的实现和边界,重点是 virtual、vptr/vtable、动态绑定、虚析构和构造析构阶段规则。
C++ 虚函数用于实现运行时多态。基类函数声明为 virtual 后,通过基类指针或引用调用该函数时,实际调用哪个实现由对象的动态类型决定。常见实现是对象里有隐藏的 vptr 指向所属类型的虚函数表 vtable,表中存放虚函数入口地址,调用时先取 vptr 再查表跳转。它的成本是对象多一个 vptr、调用多一次间接跳转并可能影响内联。使用时要注意基类指针删除派生对象时析构函数应为 virtual,构造和析构期间虚调用不会分派到尚未构造或已经析构的派生部分。
虚函数让同一个基类接口在运行时根据真实对象类型调用不同实现。只有通过基类指针或引用调用 virtual 函数时,动态绑定才有意义;普通对象直接调用或非虚函数通常是静态绑定。
主流编译器通常在含虚函数的对象中放一个隐藏 vptr,指向该类的 vtable。vtable 是类级别结构,保存各虚函数最终实现的地址。派生类重写虚函数时,对应表项指向派生类实现。
执行 `base->foo()` 时,程序先从对象中取 vptr,再在虚表中找到 foo 对应槽位,最后间接跳转到函数地址。这个过程让编译期只知道基类类型的代码,运行时仍能调用派生实现。
如果用基类指针 delete 派生对象,基类析构函数不是 virtual 时,只会按静态类型析构,派生类资源可能泄漏。作为多态基类时,析构函数通常应声明为 virtual。
构造基类部分时,派生类部分尚未构造完成;析构基类部分时,派生类部分已经析构。因此构造和析构函数内调用虚函数,不会按完整派生对象那样分派,容易产生误解和 bug。
通常对象会多一个 vptr,虚调用多一次间接寻址和跳转,且编译器更难内联。现代编译器在能证明动态类型时可能去虚化,但不能把它当成总是免费的机制。
如果类会被当作多态基类使用,外部可能通过基类指针删除派生对象。虚析构能先调用派生析构,再调用基类析构,保证资源完整释放。
重载是同一作用域内函数名相同参数不同;重写是派生类以兼容签名覆盖基类虚函数;隐藏是派生类声明同名函数遮蔽基类函数,可能并没有形成虚函数重写。
构造函数不能是虚函数。对象的动态类型需要在构造过程中逐步建立,虚表指针也随构造阶段调整,构造还没完成时不能按最终派生类型虚分派。