【c++面向对象编程】第20篇:override与final关键字:现代C++对继承的控制
目录一、一个古老的痛点二、override明确标记“我要重写”语法效果override 是“保险丝”多个基类的重写三、final到此为止1. final 类不能再有子类2. final 虚函数不能再被重写示例模板方法模式中用final固定骨架四、override final 组合使用五、与 virtual 的关系六、完整例子图形系统中的应用七、三个常见错误1. 忘记写 override签名错误悄悄隐藏2. 把 final 写在类名后面而不是前面3. 在不是虚函数的函数上使用 override/final八、现代C最佳实践九、这一篇的收获一、一个古老的痛点先看这段代码找找bugcppclass Base { public: virtual void draw(int x, int y) const; }; class Circle : public Base { public: void draw(int x, int y) { // 想重写但忘了const // 画圆的代码 } };问题Circle::draw没有const参数虽然都是(int, int)但签名不同——因为成员函数的const也是签名的一部分。结果这不是重写而是隐藏。Base::draw被藏起来了多态失效。编译器完全不会警告bug悄悄溜进代码。C11的override解决了这个问题。二、override明确标记“我要重写”语法cppclass Circle : public Base { public: void draw(int x, int y) const override { // 加override // 画圆的代码 } };效果签名正确正常编译表明这是个重写签名错误编译错误告诉你哪里不匹配cppclass Base { public: virtual void draw(int x, int y) const; virtual void resize(double factor); }; class Circle : public Base { public: void draw(int x, int y) override; // ❌ 错误缺少const void draw(int x, int y) const override; // ✅ 正确 void resize(int factor) override; // ❌ 错误参数类型应该是double };override 是“保险丝”override不改变函数的行为——它只是让编译器帮你验证。如果去掉override代码可能仍然能编译只是变成了隐藏加上override就把隐患暴露了。多个基类的重写在多继承中override同样有效cppclass Drawable { public: virtual void draw() 0; }; class Scalable { public: virtual void scale(double factor) 0; }; class Shape : public Drawable, public Scalable { public: void draw() override; // 重写 Drawable::draw void scale(double f) override; // 重写 Scalable::scale };三、final到此为止final有两个用途禁止类被继承、禁止虚函数被重写。1. final 类不能再有子类cppclass Base final { // 这个类不能被继承 // ... }; class Derived : public Base { // ❌ 编译错误Base是final的 };使用场景设计意图就是叶子类不希望有人扩展安全原因防止意外继承破坏不变量性能优化编译器可以更激进地去虚拟化2. final 虚函数不能再被重写cppclass Base { public: virtual void func() final; // 派生类不能再重写func }; class Derived : public Base { public: void func() override; // ❌ 编译错误func是final的 };使用场景基类提供了“终极”实现不允许派生类改变框架设计中的模板方法模式某些步骤固定不允许子类干预示例模板方法模式中用final固定骨架cppclass GameAI { public: // 模板方法骨架是final的不让子类改变流程 virtual void turn() final { collectResources(); buildUnits(); attack(); } protected: virtual void collectResources() 0; virtual void buildUnits() 0; virtual void attack() 0; }; class OrcAI : public GameAI { protected: void collectResources() override { /* 兽人的采集 */ } void buildUnits() override { /* 兽人的建造 */ } void attack() override { /* 兽人的攻击 */ } // turn() 不能被重写流程固定 };四、override final 组合使用可以同时使用override和finalcppclass Base { public: virtual void func(); }; class Derived : public Base { public: void func() override final; // 重写Base的func并且禁止进一步重写 }; class GrandChild : public Derived { public: void func() override; // ❌ 错误Derived::func是final的 };含义这个函数是对基类的重写同时告诉后代“不要再重写我了”。五、与 virtual 的关系virtual、override、final的使用规则关键字必须出现在作用virtual基类中首次声明声明函数为虚函数override派生类中重写函数检查是否正确重写final派生类中类或函数禁止进一步重写或继承常见组合cpp// 基类只用virtual class Base { virtual void f1(); virtual void f2(); virtual void f3(); }; // 派生类override 可选的final class Derived : public Base { void f1() override; // 普通重写 void f2() override final; // 重写且禁止再重写 void f3() final; // 可以省略override但不推荐 };最佳实践派生类中重写虚函数时永远写上override。它不改变行为但能捕获大多数签名错误。六、完整例子图形系统中的应用cpp#include iostream #include vector #include memory using namespace std; // 基类形状接口 class Shape { public: virtual void draw() const 0; virtual void resize(double factor) 0; virtual ~Shape() default; }; // 圆形可以进一步被继承 class Circle : public Shape { protected: double radius; public: Circle(double r) : radius(r) {} void draw() const override { cout 画一个半径为 radius 的圆 endl; } void resize(double factor) override { radius * factor; } }; // 带边界的圆形重写draw但不能再被继承 class BorderedCircle final : public Circle { private: double borderWidth; public: BorderedCircle(double r, double bw) : Circle(r), borderWidth(bw) {} void draw() const override final { // final阻止进一步重写 cout 画一个半径为 radius 、边框宽 borderWidth 的圆 endl; } // resize 继承了Circle的版本没有被重写 }; // 错误的示范试图继承final类 // class BadCircle : public BorderedCircle {}; // ❌ 编译错误 // 普通矩形可以被继承 class Rectangle : public Shape { protected: double w, h; public: Rectangle(double width, double height) : w(width), h(height) {} void draw() const override { cout 画一个 w x h 的矩形 endl; } void resize(double factor) override { w * factor; h * factor; } }; // 正方形重写resize但resize是final class Square final : public Rectangle { public: Square(double side) : Rectangle(side, side) {} void resize(double factor) override final { w * factor; h w; // 保持正方形 cout 正方形缩放新边长: w endl; } // 不能进一步重写resize }; int main() { vectorunique_ptrShape shapes; shapes.push_back(make_uniqueCircle(5.0)); shapes.push_back(make_uniqueBorderedCircle(3.0, 0.5)); shapes.push_back(make_uniqueRectangle(4.0, 6.0)); shapes.push_back(make_uniqueSquare(4.0)); cout 初始绘制 endl; for (const auto s : shapes) { s-draw(); } cout \n 缩放后 endl; for (auto s : shapes) { s-resize(1.5); s-draw(); } return 0; }输出text 初始绘制 画一个半径为 5 的圆 画一个半径为 3、边框宽 0.5 的圆 画一个 4x6 的矩形 画一个 4x4 的矩形 缩放后 画一个半径为 7.5 的圆 画一个半径为 4.5、边框宽 0.5 的圆 画一个 6x9 的矩形 正方形缩放新边长: 6 画一个 6x6 的矩形七、三个常见错误1. 忘记写 override签名错误悄悄隐藏cppclass Base { public: virtual void process(int x) const; }; class Derived : public Base { public: void process(int x) { // 忘了const没有override // 这是隐藏不是重写 } };加上override就能发现错误。2. 把 final 写在类名后面而不是前面cppfinal class MyClass {}; // ❌ 错误 class MyClass final {}; // ✅ 正确3. 在不是虚函数的函数上使用 override/finalcppclass MyClass { public: void func() override; // ❌ 错误基类没有对应的虚函数 void foo() final; // ❌ 错误不是虚函数不能用final };八、现代C最佳实践任何重写虚函数的地方都写上override提高代码可读性一眼看出是重写编译器帮你检查签名叶子类没有子类的类考虑标记final表达设计意图可能让编译器优化去虚拟化模板方法模式中的骨架函数可以标记final防止子类破坏算法流程不要在不需要的地方滥用final过度使用final会限制扩展性除非确定不需要继承否则保持开放九、这一篇的收获你现在应该理解override标记重写函数让编译器检查签名匹配避免隐藏bugfinal可以放在类后禁止继承或虚函数后禁止重写组合使用override final表示“重写并禁止进一步重写”最佳实践重写时永远写override叶子类考虑final 小作业找一个你以前写的继承关系的代码给所有重写的虚函数加上override。故意改错一个签名比如去掉const、改参数类型观察编译器的报错信息。尝试把某个叶子类标记为final然后试图继承它看看报错。下一篇预告第21篇《运算符重载基础语法、规则与不可重载的运算符》——结束继承章节进入运算符重载。C允许你为自定义类型定义运算符的行为比如让两个矩阵用相加。哪些运算符可以重载哪些不行有什么规则下篇开始。

相关新闻

最新新闻

日新闻

周新闻

月新闻