在 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 则是用来限制后续扩展的,只有在确实需要封锁某个虚函数或某个类时才去使用。
