C++ override 与 final

在 C++11 标准中,新增了 override 和 final 两个非常实用的说明符,专门用来辅助我们管理类的继承与虚函数重写。

简单来说,override 用来让编译器检查虚函数的重写格式是否正确,防止手误写错参数或漏掉 const。而 final 则用来给虚函数或类 “封顶”,禁止后续的子类继续重写某个函数或继续继承某个类。

使用 override 检查重写

在前面学习函数重写时我们知道,派生类函数的名称、参数列表、返回值类型以及末尾的 const 修饰都必须与基类完全一致。如果不小心写错了参数或漏掉了 const,编译器默认并不会报错,而是把它当成另一个新函数,导致多态悄悄失效。

为了避免这种隐蔽的错误,我们可以在派生类重写虚函数时,在函数声明末尾加上 override(重写)说明符。它相当于明确告诉编译器:“我这个函数就是要重写基类的同名虚函数,请帮我检查格式是否匹配。”

语法:

返回值类型 函数名(参数列表) override {
    // 函数体
}

说明:

override 紧跟在函数参数列表的后面、左花括号 { 或声明分号 ; 的前面。如果成员函数本身带有 const 修饰,那么 override 要固定写在 const 的后面,比如 void draw() const override。

加上 override 之后,只要该函数确实与基类中的某个虚函数完全匹配,代码就能正常通过编译。一旦格式不匹配,或者基类里根本没有对应的虚函数,编译器就会直接报错。

示例 1:使用 override 重写虚函数

#include <iostream>

class Control {
public:
    virtual void draw() {
        std::cout << "绘制基础控件\n";
    }
};

class Button : public Control {
public:
    void draw() override {
        std::cout << "绘制按钮控件\n";
    }
};

int main() {
    Button btn;
    Control& ctrl = btn;
    ctrl.draw();
    return 0;
}

运行结果如下。

绘制按钮控件

分析:

在这个例子中,派生类 Button 通过 void draw() override 重写了基类 Control 中的虚函数 draw()。由于函数名、参数列表和返回值类型都与基类完全一致,代码顺利通过编译,并在运行时输出 “绘制按钮控件”。

在实际开发中,只要派生类函数末尾加上了 override,函数前面就不需要再重复写 virtual 了。对于这个例子来说,下面 2 种方式是等价的:

// 方式 1(推荐)
void draw() override;

// 方式 2
virtual void draw() override;

其中,方式 1 省去了开头多余的 virtual,代码看起来更加简洁,平时我们优先使用方式 1 即可。

另外,类的虚析构函数同样支持 override。只要基类声明了虚析构函数 virtual ~Control() = default;,派生类就可以写成 ~Button() override = default;,交由编译器一并检查。

那如果不小心写错了函数格式,override 是怎么帮我们找出错误的呢?请看下面例子。

示例 2:格式不匹配触发报错

#include <iostream>

class Control {
public:
    virtual void draw() const {
        std::cout << "绘制基础控件\n";
    }
};

class Button : public Control {
public:
    // 漏写 const,编译报错
    void draw() override {
        std::cout << "绘制按钮控件\n";
    }
};

int main() {
    Button btn;
    return 0;
}

运行结果如下。

(报错)error: 'void Button::draw()' marked 'override', but does not override

分析:

从上面的报错信息可以看到,Button::draw() 虽然标记了 override,但实际上并没有重写任何基类函数。原因就在于基类的 draw() const 末尾带有 const,而派生类的 draw() 漏写了 const。

如果不加 override,这段代码不会报编译错误,而是会退化成函数隐藏,等到运行时才会发现多态失效。加上 override 之后,不管小伙伴们是漏写了 const、写错了参数类型,还是拼错了函数名,编译器都会第一时间报错提醒。

注意 override 并不会把一个普通成员函数变成虚函数,它只能用于重写基类中已有的虚函数。如果基类对应的同名函数没有加 virtual,在派生类函数末尾写 override 同样会直接编译报错。

使用 final 禁止重写虚函数

有时候,某个虚函数在当前派生类中完成重写后,我们希望把它的实现固定下来,不允许更下一层的子类再继续修改它。遇到这种需求,我们可以在该虚函数的声明末尾加上 final(最终)说明符。

语法:

返回值类型 函数名(参数列表) override final {
    // 函数体
}

说明:

当派生类既要重写基类的虚函数,又要禁止自己的子类继续重写该函数时,虽然只写 final 在语法上也行得通,但平时我们更推荐连起来写成 override final。其中,override 负责检查当前重写是否成功,final 负责禁止后续子类继续重写。

示例 3:禁止子类继续重写函数

#include <iostream>

class Control {
public:
    virtual void draw() {
        std::cout << "绘制基础控件\n";
    }
};

class Button : public Control {
public:
    // 禁止子类继续重写 draw()
    void draw() override final {
        std::cout << "绘制标准按钮\n";
    }
};

class RoundButton : public Button {
public:
    // 重写 final 函数会编译报错
    void draw() override {
        std::cout << "绘制圆角按钮\n";
    }
};

int main() {
    RoundButton btn;
    return 0;
}

运行结果如下。

(报错)error: virtual function 'virtual void RoundButton::draw()' overriding final function

分析:

void draw() override final 这句代码在 Button 类中把 draw() 的实现固定了下来。当下一层的子类 RoundButton 尝试继续重写该函数时,编译器就会直接报错,提示不能重写已标记为 final 的虚函数。

需要注意的是,给某个虚函数加上 final,只是禁止子类重写这一个函数,并不会阻止类的继承。也就是说,RoundButton 依然可以正常继承 Button 类,并在内部添加其他成员函数。

使用 final 禁止类被继承

除了用在虚函数后面,final 还可以直接写在类名的后面,用来禁止其他类继承当前这个类。

语法:

class 类名 final {
    // 类成员
};

说明:

修饰类时,final 紧跟在类名的后面,表示这个类已经是继承链的终点,不允许任何其他类再把它当作基类去继承。

如果这个类本身还继承了其他基类,那么 final 要写在当前类名的后面、英文冒号(:)的前面,也就是写成 class 派生类名 final : public 基类名。

示例 4:禁止类被继续继承

#include <iostream>

class Control {
public:
    virtual void draw() {
        std::cout << "绘制基础控件\n";
    }
};

// 禁止其他类继承 Button
class Button final : public Control {
public:
    void draw() override {
        std::cout << "绘制标准按钮\n";
    }
};

// 继承 final 类会编译报错
class RoundButton : public Button {};

int main() {
    Button btn;
    return 0;
}

运行结果如下。

(报错)error: cannot derive from 'final' base 'Button' in derived type 'RoundButton'

分析:

这里 class Button final : public Control 表明 Button 自己依然是 Control 的派生类,可以正常创建对象并参与多态。

但因为 Button 类名后面加上了 final,继承链到这里就结束了。当 RoundButton 尝试继承它时,编译器就会直接报错,提示不能从标记为 final 的基类 Button 派生出新类。

提示 在实际开发中,建议小伙伴们养成 “凡是重写基类虚函数,都显式加上 override” 的好习惯,让编译器自动帮我们检查函数格式。而 final 则是用来限制后续扩展的,只有在确实需要封锁某个虚函数或某个类时才去使用。

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号