编译时多态靠模板和函数重载,运行时多态靠虚函数;前者零开销,后者有虚表查表成本,选择取决于类型是否能在编译期确定。

编译时多态靠模板和函数重载,运行时多态靠虚函数——前者零开销,后者有虚表查表成本,选哪个取决于你能不能在写代码时就确定类型。
什么时候该用 template 而不是虚函数
当你操作的对象类型在编译期完全已知,且不同类型的实现逻辑差异大、不共享公共接口时,template 是更自然的选择。比如容器类 std::vector<int></int> 和 std::vector<:string></:string> 完全是两套独立生成的代码,互不影响。
- 常见错误现象:硬套虚函数基类去“统一”处理
int、double、std::complex<float></float>等根本没继承关系的类型,结果被迫写一堆空实现或dynamic_cast检查 - 使用场景:
std::sort、std::make_shared、数值计算库(如 Eigen)大量依赖模板推导类型,避免运行时分支 - 性能影响:模板实例化会增大二进制体积,但每次调用都是直接跳转,无虚表查找、无指针间接访问
为什么 virtual 函数不能是 inline 或模板成员
因为 virtual 的本质是运行时绑定,而 inline 是编译器对“确定调用目标”的优化提示,两者逻辑冲突;模板成员函数若声明为 virtual,编译器无法在实例化前确定虚表布局。
- 常见错误现象:在模板类里写
virtual void foo() = 0;,编译报错invalid use of template-name without an argument list - 参数差异:虚函数只能是非静态成员函数,不能有默认参数(容易因动态绑定导致语义混乱),也不能是构造函数或析构函数以外的特殊成员(析构函数除外,且应为
virtual) - 兼容性影响:含虚函数的类对象大小会增加(至少一个虚指针),且不能用
memcpy安全复制,也不能作为 C 接口结构体直接传递
override 和 final 不只是语法糖,它们堵住了哪些坑
没有 override 时,拼错函数名、参数类型不匹配(比如把 const std::string& 写成 std::string&)、遗漏 const 限定符,都会悄无声息地创建新函数而非覆盖,导致多态失效。
立即学习“C++免费学习笔记(深入)”;
- 常见错误现象:子类写了
void draw();,父类是virtual void draw() const;,调用时走的仍是父类实现,调试时才发现根本没进子类函数 - 使用场景:只要继承了带虚函数的基类,子类重写必须加
override;明确不希望被进一步派生时,在类名后加final(如class Button final : public Widget) - 性能影响:
final可让编译器在某些情况下取消虚调用,内联子类函数——但仅当调用点能静态确定对象确切类型时才生效
虚函数调用的开销本身很小,但真正拖慢性能的往往是它带来的间接性和阻碍优化:编译器不敢内联、不敢向量化、难以做跨函数分析。而模板泛化一旦滥用,又会导致编译时间暴涨和链接时符号爆炸。权衡不在“快或慢”,而在“你是否需要在运行时接受未知类型”——如果答案是否定的,就别碰虚函数。











