diff --git a/book/en/src/SUMMARY.md b/book/en/src/SUMMARY.md index 24225ac..c00c8f7 100644 --- a/book/en/src/SUMMARY.md +++ b/book/en/src/SUMMARY.md @@ -11,6 +11,7 @@ - [Type Deduction - auto and decltype](./cpp11/00-auto-and-decltype.md) - [Defaulted and Deleted Functions](./cpp11/01-default-and-delete.md) +- [final and override - Explicit Control of Virtual Function Behavior](./cpp11/02-final-and-override.md) - [List Initialization](./cpp11/09-list-initialization.md) - [Delegating Constructors](./cpp11/10-delegating-constructors.md) - [Inherited Constructors](./cpp11/11-inherited-constructors.md) diff --git a/book/en/src/cpp11/02-final-and-override.md b/book/en/src/cpp11/02-final-and-override.md new file mode 100644 index 0000000..bbbf0d2 --- /dev/null +++ b/book/en/src/cpp11/02-final-and-override.md @@ -0,0 +1,193 @@ +
+ + 🌎 [中文] | [English] +
+ +[中文]: ../../cpp11/02-final-and-override.html +[English]: ./02-final-and-override.html + +# final and override - Explicit Control of Virtual Function Behavior + +final and override are two **context-sensitive identifiers** introduced in C++11, used in virtual-function inheritance to explicitly express the intent of **overriding** and **sealing**, allowing the compiler to surface polymorphism mismatches at compile time that would otherwise only show up as runtime bugs. + +| Book | Video | Code | X | +| --- | --- | --- | --- | +| [cppreference-final](https://en.cppreference.com/w/cpp/language/final) / [cppreference-override](https://en.cppreference.com/w/cpp/language/override) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/en/src/cpp11/02-final-and-override.md) | [Video Explanation]() | [Practice Code](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-0.cpp) | | + + +**Why were they introduced?** + +- Before C++11, whether a derived class actually overrode a base virtual function relied entirely on programmers checking signatures by hand — a single mismatched parameter would silently turn an override into a name-hiding declaration with no compiler warning +- There was no standard way to express the design intent that "this type, or this polymorphic chain, ends here" +- They make the design contract of virtual functions readable and verifiable + +**What's the difference between the two?** + +- override: applied after a derived-class member function, explicitly declaring "this function overrides a base-class virtual function", so the compiler can verify it +- final: applied after a virtual function means "this virtual function cannot be further overridden"; applied after a class means "this class cannot be inherited from" + +## I. Basic Usage and Scenarios + +### override - Explicitly Declare an Override + +Without override, even a typo in the derived signature only becomes "a brand-new ordinary function" — the polymorphic behavior is silently lost. + +```cpp +struct Base { + virtual void func(int) { } +}; + +struct Derived : Base { + void func(double) { } // intended to override, but the parameter type is wrong; + // this actually declares a new function +}; +``` + +With override, the same mistake is rejected at compile time. + +```cpp +struct Derived : Base { + void func(double) override; // error: no matching virtual function in any base class +}; +``` + +Only a base virtual function whose signature (return type + parameter list + cv-qualifiers + ref-qualifiers) matches exactly will satisfy override. + +```cpp +struct Base { + virtual void func(int); +}; + +struct Derived : Base { + void func(int) override; // ok +}; +``` + +### final - Forbid Further Overriding or Inheritance + +final has two usages targeting different things. + +**On a virtual function - cut off the polymorphic chain** + +```cpp +struct A { + virtual void func() final { } +}; + +struct B : A { + void func() override; // error: A::func is final and cannot be overridden +}; +``` + +**On a class - forbid inheritance** + +```cpp +struct B final { }; + +struct C : B { }; // error: B is final and cannot be inherited from +``` + +### final + Pure Virtual - Non-Overridable Template Method (NVI) + +Lock the outer interface with `virtual ... final`, and expose the customizable steps as pure virtual functions. The result is a stable interface where **the execution order cannot be changed but each step is customizable**. This is a concise expression of the **Non-Virtual Interface** idiom. + +```cpp +struct AudioPlayer { + virtual void play() final { // subclasses cannot change the overall flow of play + init_audio_params(); + play_audio(); + } +private: + virtual void init_audio_params() = 0; // left for subclasses to customize + virtual void play_audio() = 0; +}; + +struct WAVPlayer : AudioPlayer { + void init_audio_params() override { /* ... */ } + void play_audio() override { /* ... */ } +}; + +struct MP3Player : AudioPlayer { + void init_audio_params() override { /* ... */ } + void play_audio() override { /* ... */ } +}; +``` + +Callers always use the unified `AudioPlayer::play()`; each format's player only needs to implement the two hooks. This structure is common when designing plugin-style or protocol-style interfaces. + +### Context-Sensitive Identifiers + +Neither override nor final is a reserved word or a keyword — they are **context-sensitive identifiers**. They only carry these meanings when they appear at specific positions in a virtual function declaration or a class declaration; in any other position they can still be used as variable names, type names, namespace names, etc. + +```cpp +B override; // ok: here override is just an ordinary variable name +B final; // ok: here final is just an ordinary variable name +``` + +This is a deliberate compromise in the C++ standard for backward compatibility: existing code that uses override or final as identifiers won't fail to compile after upgrading to C++11. + +## II. Important Notes + +### override Requires a Signature-Matching Base Virtual Function + +Once override is added to a derived-class member function, the compiler requires **a virtual function with a matching signature** to exist in some base class — otherwise it's a compile error. This is the core value of override: lifting "override mismatch" silent bugs from runtime to compile time. + +```cpp +struct A { + virtual void func1() { } + void func2() { } // note: not virtual +}; + +struct B : A { + void func1() override; // ok + void func2() override; // error: A::func2 is not virtual +}; +``` + +### A final Class Is "Sealed" - Use With Care + +A final class cannot be inherited from at all, not even to add a couple of helper methods. Marking a class final is essentially committing to "this type is, by design, a leaf node". Some rules of thumb: + +- The type is explicitly not meant to be extended further (e.g. error types, framework-internal implementation classes, singletons) -> a good fit for final +- A general-purpose base class or a framework-provided extension point -> do not casually add final + +### final Only Applies to Virtual Functions + +An ordinary member function cannot be overridden in the first place, so adding final to it is meaningless and the compiler will reject it. + +```cpp +struct A { + void func() final; // error: final cannot be applied to a non-virtual function +}; +``` + +### override and final Can Be Used Together + +If a virtual function should both override the base-class version and forbid further overriding in derived classes, you can combine the two. + +```cpp +struct B : A { + void func() override final; // overrides A::func, and prevents C from overriding it again +}; +``` + +## III. Practice Code + +### Practice Topics + +- 0 - [Familiarize with override](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-0.cpp) +- 1 - [Familiarize with final](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-1.cpp) +- 2 - [final + Template Method - AudioPlayer / WAV / MP3 / OGG](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-2.cpp) + +### Practice Code Auto-detection Command + +``` +d2x checker final-and-override +``` + +## IV. Additional Resources + +- [Discussion Forum](https://forum.d2learn.org/category/20) +- [d2mcpp Tutorial Repository](https://github.com/mcpp-community/d2mcpp) +- [Tutorial Video List](https://space.bilibili.com/65858958/lists/5208246) +- [Tutorial Support Tool - xlings](https://github.com/d2learn/xlings) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 8b57b60..5060c10 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -11,6 +11,7 @@ - [auto和decltype - 类型自动推导](./cpp11/00-auto-and-decltype.md) - [...](./cpp11/01-default-and-delete.md) +- [final 和 override - 虚函数重写控制](./cpp11/02-final-and-override.md) - [列表初始化](./cpp11/09-list-initialization.md) - [委托构造函数](./cpp11/10-delegating-constructors.md) - [继承构造函数](./cpp11/11-inherited-constructors.md) diff --git a/book/src/cpp11/02-final-and-override.md b/book/src/cpp11/02-final-and-override.md new file mode 100644 index 0000000..209af04 --- /dev/null +++ b/book/src/cpp11/02-final-and-override.md @@ -0,0 +1,192 @@ +
+ + 🌎 [中文] | [English] +
+ +[中文]: ./02-final-and-override.html +[English]: ../en/cpp11/02-final-and-override.html + +# final 和 override - 虚函数重写控制 + +final 和 override 是 C++11 引入的两个 **上下文相关标识符**, 用于在虚函数继承中显式表达 **重写** 和 **封口** 的意图, 让编译器在编译期就暴露原本只能在运行期才能发现的多态错位问题 + +| Book | Video | Code | X | +| --- | --- | --- | --- | +| [cppreference-final](https://en.cppreference.com/w/cpp/language/final) / [cppreference-override](https://en.cppreference.com/w/cpp/language/override) / [markdown](https://github.com/mcpp-community/d2mcpp/blob/main/book/src/cpp11/02-final-and-override.md) | [视频解读]() | [练习代码](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-0.cpp) | | + + +**为什么引入?** + +- 在 C++11 之前, "派生类有没有真的重写到基类的虚函数" 完全靠程序员自己核对签名, 一个参数写错就会从重写变成隐藏, 编译器不会报错 +- 缺少标准方式来表达 "这个类型 / 这条多态链到此为止" 的设计意图 +- 让虚函数的设计契约变得可读、可校验 + +**两者的语义区别?** + +- override: 加在派生类成员函数后, 显式声明 "这个函数是在重写基类的虚函数", 让编译器协助检查 +- final: 加在虚函数后表示 "这个虚函数不能再被派生类重写", 加在类后表示 "这个类不能再被继承" + +## 一、基础用法和场景 + +### override - 显式声明重写 + +不加 override 时, 派生类哪怕签名写错也只是 "新增了一个普通函数", 多态行为悄悄丢失 + +```cpp +struct Base { + virtual void func(int) { } +}; + +struct Derived : Base { + void func(double) { } // 本来想重写, 但参数类型不一致, 实际是新声明了一个函数 +}; +``` + +加上 override 后, 同样的错误会在编译期被拒绝 + +```cpp +struct Derived : Base { + void func(double) override; // 错误: 没有签名匹配的基类虚函数 +}; +``` + +只有签名 (返回类型 + 参数列表 + cv 限定 + 引用限定) 完全匹配的基类虚函数, 才会让 override 通过 + +```cpp +struct Base { + virtual void func(int); +}; + +struct Derived : Base { + void func(int) override; // ok +}; +``` + +### final - 禁止后续重写或派生 + +final 有两种用法, 作用对象不同 + +**修饰虚函数 - 截断多态链** + +```cpp +struct A { + virtual void func() final { } +}; + +struct B : A { + void func() override; // 错误: A::func 已被 final, 不能再重写 +}; +``` + +**修饰类 - 禁止派生** + +```cpp +struct B final { }; + +struct C : B { }; // 错误: B 已被 final, 不能被继承 +``` + +### final + 纯虚 - 不可改写的模板方法 (NVI) + +把外层接口用 `virtual ... final` 锁住, 把可定制的步骤暴露成纯虚函数, 就得到一个 "执行顺序不可改、但每一步可定制" 的稳定接口. 这是 **非虚接口惯用法 (Non-Virtual Interface)** 的一种简洁写法 + +```cpp +struct AudioPlayer { + virtual void play() final { // 子类不能改写 play 的整体流程 + init_audio_params(); + play_audio(); + } +private: + virtual void init_audio_params() = 0; // 留给子类定制 + virtual void play_audio() = 0; +}; + +struct WAVPlayer : AudioPlayer { + void init_audio_params() override { /* ... */ } + void play_audio() override { /* ... */ } +}; + +struct MP3Player : AudioPlayer { + void init_audio_params() override { /* ... */ } + void play_audio() override { /* ... */ } +}; +``` + +调用方拿到的是统一的 `AudioPlayer::play()`, 不同格式的播放器只需实现两个钩子. 这种结构在做插件式 / 协议式接口时很常见 + +### 上下文相关标识符 + +override 和 final 既不是保留字, 也不是关键字, 而是 **上下文相关标识符 (context-sensitive identifiers)**. 只有出现在虚函数声明或类声明的特定位置时才被解释为这两种语义, 普通位置可以正常用作变量名、类型名、命名空间名等 + +```cpp +B override; // ok: 这里 override 只是个普通变量名 +B final; // ok: 这里 final 只是个普通变量名 +``` + +这也是 C++ 标准为了向后兼容做的折中: 已有代码里用 override / final 当标识符的, 不会因为升级到 C++11 而编译失败 + +## 二、注意事项 + +### override 必须有签名一致的基类虚函数 + +派生类成员函数加上 override 后, 编译器会要求在某个基类中存在 **签名一致的虚函数**, 否则编译报错. 这是 override 最核心的价值: 把 "重写错位" 这种 silent bug 从运行期提前到编译期 + +```cpp +struct A { + virtual void func1() { } + void func2() { } // 注意: 不是 virtual +}; + +struct B : A { + void func1() override; // ok + void func2() override; // 错误: A::func2 不是 virtual +}; +``` + +### final 类是 "封口" - 慎用 + +final 类不能被继承, 哪怕只是想派生一个加几个辅助方法的子类也不行. 给一个类加 final, 实际上是在做 "这个类型在设计上就是叶子节点" 的承诺, 需要谨慎. 经验法则: + +- 业务上明确不希望被继续派生 (例如错误类型 / 框架内部实现类 / 单例) -> 适合 final +- 通用基类 / 框架预留的可扩展点 -> 不要轻易加 final + +### final 只能用在虚函数上 + +普通成员函数本来就不能被 override, 给它加 final 没有意义, 编译器也会拒绝 + +```cpp +struct A { + void func() final; // 错误: 非虚函数上不能用 final +}; +``` + +### override 和 final 可以同时出现 + +如果某个虚函数既要重写基类版本, 又要禁止再被派生类继续重写, 可以两者一起加 + +```cpp +struct B : A { + void func() override final; // 重写 A::func, 同时禁止 C 再重写 +}; +``` + +## 三、练习代码 + +### 练习代码主题 + +- 0 - [熟悉 override 的使用](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-0.cpp) +- 1 - [熟悉 final 的使用](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-1.cpp) +- 2 - [final + 模板方法 - AudioPlayer / WAV / MP3 / OGG](https://github.com/mcpp-community/d2mcpp/blob/main/dslings/cpp11/02-final-and-override-2.cpp) + +### 练习代码自动检测命令 + +``` +d2x checker final-and-override +``` + +## 四、其他 + +- [交流讨论](https://forum.d2learn.org/category/20) +- [d2mcpp教程仓库](https://github.com/mcpp-community/d2mcpp) +- [教程视频列表](https://space.bilibili.com/65858958/lists/5208246) +- [教程支持工具-xlings](https://github.com/d2learn/xlings)