面试题答案
一键面试内存布局
- 基类A:
- 基类A的对象在内存中,首先会有一个虚指针(vptr),这个虚指针指向虚函数表(vtable)。
- 虚函数表是一个函数指针数组,其中包含了基类A中虚函数的地址,在本题中就是
func
函数的地址。
- 派生类B:
- 派生类B的对象内存布局中,最开始同样是一个虚指针(vptr),它指向派生类B自己的虚函数表。
- 派生类B的虚函数表内容,首先是重写后的
func
函数地址(覆盖了基类A虚函数表中func
的地址),如果B类还有其他新增的虚函数,其地址也会依次排列在虚函数表中。
- 派生类C:
- 派生类C的对象内存布局与B类似,开头是虚指针(vptr),指向C类自己的虚函数表。
- C类虚函数表中
func
函数的地址是C类重写后的func
函数地址,同样若有新增虚函数,其地址也会按顺序排列。
性能开销及优化
- 额外开销:
- 间接函数调用开销:由于通过虚函数表进行函数调用,需要先通过虚指针找到虚函数表,再从虚函数表中获取函数地址,然后进行函数调用,这相比于普通的直接函数调用,增加了至少两次内存访问(一次获取虚函数表地址,一次获取函数地址),在现代处理器架构下,这可能会导致缓存不命中,影响性能。
- 对象内存占用增加:每个对象都需要额外存储一个虚指针,在对象数量较多时,会增加内存占用,尤其在内存受限的环境中可能成为问题。
- 优化方式:
- 使用静态多态(模板):在编译期确定函数调用,避免运行时的虚函数表查找。例如使用模板元编程实现编译期多态,适用于在编译时就能确定类型的场景,如
std::vector
的不同类型实例化。 - 减少虚函数层次:尽量减少类继承体系中虚函数的层次,层次越多,虚函数表查找的潜在开销越大。可以通过合理设计类结构,减少不必要的继承。
- 对象池技术:对于频繁创建和销毁包含虚函数的对象场景,可以使用对象池技术,减少内存分配和释放的开销,一定程度上缓解虚指针带来的内存占用压力。
- 使用静态多态(模板):在编译期确定函数调用,避免运行时的虚函数表查找。例如使用模板元编程实现编译期多态,适用于在编译时就能确定类型的场景,如