【c++面向对象编程】第20篇:override与final关键字:现代C++对继承的控制

目录

一、一个古老的痛点

二、override:明确标记"我要重写"

语法

效果

[override 是"保险丝"](#override 是“保险丝”)

多个基类的重写

三、final:到此为止

[1. final 类:不能再有子类](#1. final 类:不能再有子类)

[2. final 虚函数:不能再被重写](#2. final 虚函数:不能再被重写)

示例:模板方法模式中用final固定骨架

[四、override + final 组合使用](#四、override + final 组合使用)

[五、与 virtual 的关系](#五、与 virtual 的关系)

六、完整例子:图形系统中的应用

七、三个常见错误

[1. 忘记写 override,签名错误悄悄隐藏](#1. 忘记写 override,签名错误悄悄隐藏)

[2. 把 final 写在类名后面而不是前面](#2. 把 final 写在类名后面而不是前面)

[3. 在不是虚函数的函数上使用 override/final](#3. 在不是虚函数的函数上使用 override/final)

八、现代C++最佳实践

九、这一篇的收获


一、一个古老的痛点

先看这段代码,找找bug:

cpp

复制代码
class 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悄悄溜进代码。

C++11的 override 解决了这个问题。


二、override:明确标记"我要重写"

语法

cpp

复制代码
class Circle : public Base {
public:
    void draw(int x, int y) const override {  // 加override
        // 画圆的代码
    }
};

效果

  • 签名正确:正常编译,表明这是个重写

  • 签名错误:编译错误,告诉你哪里不匹配

cpp

复制代码
class 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 同样有效:

cpp

复制代码
class 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 类:不能再有子类

cpp

复制代码
class Base final {   // 这个类不能被继承
    // ...
};

class Derived : public Base {   // ❌ 编译错误!Base是final的
};

使用场景

  • 设计意图就是叶子类,不希望有人扩展

  • 安全原因:防止意外继承破坏不变量

  • 性能优化:编译器可以更激进地去虚拟化

2. final 虚函数:不能再被重写

cpp

复制代码
class Base {
public:
    virtual void func() final;   // 派生类不能再重写func
};

class Derived : public Base {
public:
    void func() override;   // ❌ 编译错误!func是final的
};

使用场景

  • 基类提供了"终极"实现,不允许派生类改变

  • 框架设计中的模板方法模式:某些步骤固定,不允许子类干预

示例:模板方法模式中用final固定骨架

cpp

复制代码
class 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 组合使用

可以同时使用 overridefinal

cpp

复制代码
class 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 的关系

virtualoverridefinal 的使用规则:

关键字 必须出现在 作用
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() {
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Circle>(5.0));
    shapes.push_back(make_unique<BorderedCircle>(3.0, 0.5));
    shapes.push_back(make_unique<Rectangle>(4.0, 6.0));
    shapes.push_back(make_unique<Square>(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,签名错误悄悄隐藏

cpp

复制代码
class Base {
public:
    virtual void process(int x) const;
};

class Derived : public Base {
public:
    void process(int x) {  // 忘了const,没有override
        // 这是隐藏,不是重写!
    }
};

加上 override 就能发现错误。

2. 把 final 写在类名后面而不是前面

cpp

复制代码
final class MyClass {};   // ❌ 错误
class MyClass final {};   // ✅ 正确

3. 在不是虚函数的函数上使用 override/final

cpp

复制代码
class MyClass {
public:
    void func() override;   // ❌ 错误:基类没有对应的虚函数
    void foo() final;       // ❌ 错误:不是虚函数不能用final
};

八、现代C++最佳实践

  1. 任何重写虚函数的地方,都写上 override

    • 提高代码可读性(一眼看出是重写)

    • 编译器帮你检查签名

  2. 叶子类(没有子类的类)考虑标记 final

    • 表达设计意图

    • 可能让编译器优化(去虚拟化)

  3. 模板方法模式中的骨架函数可以标记 final

    • 防止子类破坏算法流程
  4. 不要在不需要的地方滥用 final

    • 过度使用 final 会限制扩展性

    • 除非确定不需要继承,否则保持开放


九、这一篇的收获

你现在应该理解:

  • override:标记重写函数,让编译器检查签名匹配,避免隐藏bug

  • final:可以放在类后(禁止继承)或虚函数后(禁止重写)

  • 组合使用override final 表示"重写并禁止进一步重写"

  • 最佳实践 :重写时永远写 override,叶子类考虑 final

💡 小作业:找一个你以前写的继承关系的代码,给所有重写的虚函数加上 override。故意改错一个签名(比如去掉 const、改参数类型),观察编译器的报错信息。尝试把某个叶子类标记为 final,然后试图继承它,看看报错。


下一篇预告 :第21篇《运算符重载基础:语法、规则与不可重载的运算符》------结束继承章节,进入运算符重载。C++允许你为自定义类型定义运算符的行为,比如让两个矩阵用 + 相加。哪些运算符可以重载?哪些不行?有什么规则?下篇开始。

相关推荐
caimouse1 小时前
Reactos 第 4 章 对象管理 — 4.8 系统调用 NtDuplicateObject / 4.9 系统调用 NtClose
开发语言·windows·架构
xieliyu.8 小时前
Java算法精讲:双指针(二)
java·开发语言·算法
苏宸啊9 小时前
IPC管道
linux·c++
何以解忧,唯有..9 小时前
Python包管理工具pip:从入门到精通
开发语言·python·pip
BestOrNothing_20159 小时前
ROS2 话题通信实战:消息对象、Publisher 发布器与 Subscriber 订阅器保姆级教程
c++·ros2·subscriber·publisher·话题通信
雪的季节9 小时前
RabbitMQ详解
开发语言
ice81303318110 小时前
【Python】Matplotlib折线图绘制
开发语言·python·matplotlib
三品吉他手会点灯10 小时前
C语言学习笔记 - 44.运算符和表达式 - 运算符2 - 除法与取余运算符
c语言·开发语言·笔记·算法
kkeeper~10 小时前
0基础C语言积跬步之动态内存管理
c语言·开发语言
橘右今10 小时前
2026 Java后端高频面试宝典
java·开发语言·面试