final修饰类时禁止继承,编译时报错;override用于虚函数重写校验,签名不匹配则编译失败;二者可共用于虚函数,表示重写且禁止进一步重写,纯编译期检查,零运行开销。

final 修饰类时,子类继承会直接编译失败
加了 final 的类不能被继承,这是最硬的限制。编译器会在你写 class Derived : public Base {} 这种代码时,立刻报错,而不是等到运行时才发现问题。
常见错误现象:error: cannot derive from 'final' base class 'Base' —— 这不是警告,是编译中断。
使用场景:封装核心不可扩展类型,比如 std::string_view 内部实现类、单例的基类、或明确禁止多态延伸的工具类。
注意点:final 是类定义的一部分,必须写在类名后面,不能写在花括号外:class Base final { /* ... */ }; ✅class Base { /* ... */ } final; ❌(语法错误)
立即学习“C++免费学习笔记(深入)”;
override 只用于虚函数声明,且必须和基类签名完全一致
override 不是修饰符,是“契约校验标记”:它告诉编译器“我这里写的这个函数,必须能在基类里找到一个匹配的虚函数”,否则就报错。
常见错误现象:
- 参数类型差一个 const 或引用符号 → 报 error: 'func' does not override any member functions
- 返回类型协变没处理好(比如基类返回 Base*,派生类返回 Derived* 但没写 override)→ 编译通过但实际没重写
- 忘了基类函数没加 virtual → override 直接失效,变成新函数
实操建议:
- 所有你主观意图是“重写”的虚函数,都加上 override
- 基类对应函数必须有 virtual(或来自 = 0 纯虚),且 const / noexcept / 参数类型逐位匹配
- 示例:
class Base {
public:
virtual void process(int x) const noexcept = 0;
};
class Derived : public Base {
public:
void process(int x) const noexcept override { /* OK */ }
void process(int x) override { /* ❌ 缺 const,编译失败 */ }
};
final 和 override 能一起用,但只对虚函数有效
你可以在虚函数声明末尾同时写 final override,意思是“我重写了它,而且禁止后续任何子类再重写”。顺序不重要,但两者共存时,override 必须在,否则 final 单独出现会被误认为是修饰类(语法上不冲突,但语义错乱)。
容易踩的坑:
- 在非虚函数上加 final 没意义(C++ 不允许对非虚函数用 final 修饰)
- 在普通成员函数上加 override → 直接编译失败,因为找不到可重写的基类虚函数
- 多重继承时,如果两个基类都有同名虚函数,override 只能匹配其中一个,另一个可能被意外隐藏(这时要显式用作用域解析)
性能影响:零开销。它们纯属编译期检查,不生成额外指令,也不影响 vtable 布局。
不加 override 的重写,可能静默失败或行为错乱
没有 override 时,C++ 允许你“看似重写”实则定义新函数:比如拼错函数名、参数少个 &、返回类型不协变——编译器不会提醒,对象调用时走的还是基类版本,而你调试半天以为逻辑写错了。
典型场景:
- 重构基类函数签名后,忘记同步更新所有派生类
- 团队协作中,有人新增虚函数但文档/注释没跟上
- 使用 IDE 自动生成重写时,漏掉 const 或 noexcept
建议:
- 编译器加 -Woverloaded-virtual(GCC/Clang)辅助发现隐藏函数
- CI 流程中启用 -Werror=overloaded-virtual 强制拦截
- 所有虚函数接口变更,必须 grep 全项目找 override 并逐个验证
最麻烦的不是写错,是写得“差不多像对”,然后跑几天才暴露逻辑偏差。










