一、基本概念
1. 什么是虚函数(virtual function)?
虚函数是用 virtual
关键字修饰的成员函数,支持运行时多态(dynamic polymorphism)。通过基类指针或引用调用派生类重写的函数。
class Base {
public:
virtual void speak() { cout << "Base speaking" << endl; }
};
只要类中存在虚函数,编译器就会为该类生成虚表。
2. 什么是虚表(vtable)?
- vtable 是一个函数指针数组,用于保存该类的所有虚函数的地址。
- 每个有虚函数的类有一张虚表。
- 如果派生类重写了虚函数,虚表中对应的函数指针将被替换为派生类版本。
3. 什么是虚表指针(vptr)?
- 每个对象中都有一个隐藏成员变量
vptr
(虚表指针),指向该对象所属类的虚表。 - 对象创建时,构造函数会自动设置
vptr
的值。 - 调用虚函数时,程序会通过
vptr
找到虚表,再通过表中函数指针找到目标函数,最终调用。
二、图解原理(简化示意)
对象结构: 虚表结构(函数地址表):
[对象内存]
+---------+ +------------------+
| vptr | -----> | &Derived::speak()|
+---------+ +------------------+
三、示例代码与解析
示例:基类与派生类使用虚函数
#include <iostream>
using namespace std;
class Base {
public:
virtual void speak() {
cout << "Base::speak()" << endl;
}
};
class Derived : public Base {
public:
void speak() override {
cout << "Derived::speak()" << endl;
}
};
int main() {
Base* p = new Derived(); // p 的 vptr 指向 Derived 的虚表
p->speak(); // 动态绑定,输出 Derived::speak()
delete p;
return 0;
}
执行过程(底层原理):
- 创建
Derived
对象,构造函数自动设置vptr
,指向Derived
的虚表; - 虚表中
speak()
的指针是&Derived::speak()
; - 调用
p->speak()
,程序先通过vptr
找到虚表,再调用函数指针,最终运行Derived::speak()
。
四、进一步理解:虚表模拟(伪代码)
下面是 C++ 编译器幕后自动完成的模拟行为:
class Base {
void** vptr; // 隐藏成员:虚表指针
static void* vtable_Base[] = { &Base::speak };
public:
virtual void speak();
};
class Derived : public Base {
static void* vtable_Derived[] = { &Derived::speak };
public:
void speak() override;
};
你不会在代码中显式看到 vptr
和 vtable
,它们是编译器隐藏实现的。
五、相关细节注意
情况 | 说明 |
---|---|
类没有虚函数 | 不生成 vtable,不支持运行时多态 |
虚函数未被重写 | 虚表中仍然是基类函数指针 |
多重继承 | 每个父类一套虚表和一个 vptr |
析构函数建议设为 virtual | 防止只析构父类对象导致内存泄漏 |
构造函数中调用虚函数 | 不会发生多态,vptr 还没完全初始化 |
六、下节预告
1.多重继承下的虚表结构
2.析构函数不设为virtual导致内存泄漏示例