C++虚函数绑定与虚表机制解析:为何输出Hello Derived?
Hello Derived? 先把可编译的代码贴出来方便参考(原代码漏了必要的头文件,我补上了):
#include <iostream> using namespace std; class Base { public: int a; virtual void fun1() { cout<<"Hello Base"<<endl; } void fun2() { fun1(); } }; class Derived: public Base { int a; void fun1() { cout<<"Hello Derived"<<endl; } }; int main() { Base * B = new Derived; B->fun2(); return 1; }
一、核心原因:虚函数的动态绑定
虽然fun2是Base类的非虚成员函数,编译器会对它做静态绑定,但fun2内部调用的fun1是虚函数——这才是关键!当B指向一个Derived对象时,运行时会根据对象的实际类型,调用Derived重写后的fun1,而不是Base的版本。
二、函数绑定的完整过程
咱们一步步捋运行时的逻辑:
编译阶段:
B->fun2()的静态绑定
因为fun2不是虚函数,编译器在编译时就能确定:不管B指向什么对象,B->fun2()调用的一定是Base::fun2。这就是静态绑定(编译时绑定)。运行阶段:
fun1()的动态绑定
当Base::fun2执行到fun1();时,这里的fun1等价于this->fun1()。而this指针的实际指向是Derived对象(因为B指向的是new Derived)。
由于fun1是虚函数,编译器不会直接调用Base::fun1,而是通过this指向的对象里的虚表指针(vptr),去查找对应的**虚函数表(vtable)**中的fun1条目。此时this指向的Derived对象的vptr指向Derived的vtable,里面的fun1条目是Derived::fun1的地址,所以最终调用的是Derived的版本。
三、Base和Derived类的虚表创建细节
虚表(vtable)是C++实现动态多态的底层机制,编译器会自动为带虚函数的类生成:
Base类的虚表
- 当编译器发现Base类有虚函数
fun1时,会为Base生成一个独立的虚表。 - 这个虚表里只包含Base类所有虚函数的地址,这里就是
&Base::fun1。 - 每个Base类对象都会包含一个隐藏的
vptr(虚表指针),在Base对象构造时,vptr会被初始化指向Base的vtable。
Derived类的虚表
- Derived继承自Base,并且重写了
fun1,所以编译器会为Derived生成自己的虚表。 - 生成时,先复制Base虚表的所有条目,然后把被重写的
fun1条目替换为&Derived::fun1的地址。 - Derived对象构造时,先调用Base的构造函数(此时
vptr被设为Base的vtable),然后进入Derived的构造函数,把vptr更新为指向Derived自己的vtable。
所以当B指向Derived对象时,虽然B是Base*类型,但对象本身的vptr指向的是Derived的vtable,调用虚函数时自然会找到Derived的fun1。
内容的提问来源于stack exchange,提问作者Ayush Gupta

