虚函数表(vtable)和虚函数指针(vptr)在实现多态过程中的作用机制
- 虚函数表(vtable)
- 每个包含虚函数的类或其派生类都有一个虚函数表。虚函数表是一个数组,其中存放了该类所有虚函数的地址。
- 当类中定义了虚函数时,编译器会为该类生成虚函数表。如果派生类重写了基类的虚函数,虚函数表中对应位置会替换为派生类重写的虚函数地址;如果没有重写,则沿用基类虚函数的地址。
- 虚函数表使得程序在运行时能够根据对象的实际类型找到正确的虚函数版本。
- 虚函数指针(vptr)
- 每个包含虚函数的类的对象都有一个虚函数指针。这个指针在对象构造时被初始化,指向该对象所属类的虚函数表。
- 通过对象的虚函数指针,程序可以间接访问到虚函数表,进而在运行时根据虚函数表中存放的虚函数地址来调用正确的虚函数。例如,当通过基类指针或引用调用虚函数时,程序会首先通过虚函数指针找到虚函数表,然后根据虚函数表中对应虚函数的地址进行函数调用,实现动态绑定和多态。
在复杂继承体系中优化虚函数调用性能的策略
- 使用非虚接口(NVI)惯用法
- 定义一个非虚的公有成员函数,在这个函数内部调用虚函数。这样,外部代码调用的是非虚函数,减少了虚函数调用的开销。同时,虚函数可以保持为保护成员,以便派生类重写。
- 示例代码:
class Base {
public:
void doWork() {
// 非虚接口
preWork();
doActualWork();
postWork();
}
protected:
virtual void doActualWork() {
// 实际工作的虚函数
}
void preWork() {
// 预处理工作
}
void postWork() {
// 后处理工作
}
};
- 减少虚函数的层数
- 在继承体系中,尽量减少虚函数的重写层数。因为每一层重写都增加了查找虚函数表的复杂性。如果可能,将相关功能封装到较少层次的类中,减少不必要的继承和虚函数重写。
- 使用缓存
- 对于频繁调用虚函数且参数相同的场景,可以缓存虚函数的调用结果。例如,可以使用
std::unordered_map
来缓存参数和对应的虚函数调用结果,避免重复调用虚函数。
- 示例代码:
class MyClass {
public:
virtual int expensiveVirtualFunction(int param) {
// 复杂计算
return result;
}
private:
std::unordered_map<int, int> cache;
};
class DerivedClass : public MyClass {
public:
int expensiveVirtualFunction(int param) override {
if (cache.find(param) != cache.end()) {
return cache[param];
}
int result = /* 复杂计算 */;
cache[param] = result;
return result;
}
};
- 避免在构造函数和析构函数中调用虚函数
- 在构造函数和析构函数中调用虚函数不会实现多态,因为此时对象的类型尚未完全构造或已经部分析构。这可能导致未定义行为,并且无法利用虚函数表机制优化调用,所以应尽量避免这种情况。