概念
final和override这两个关键字应该是比较少人注意的特性,相比其他的也更简单些,这两个特性都能让我们的程序在继承类和覆写虚函数时更安全,更清晰。
final
如果我们定义的一个虚函数不想被派生类覆盖(重写),那么可以在虚函数之后添加一个final关键字,声明这个虚函数不可以被派生类所覆盖(重写),如下:
class Base
{
virtual void foo();
virtual void foo2() final;
virtual int foo3();
void bar();
};
class Derived : public Base
{
void foo() final; // OK: foo 被override并且是最后一个override,在其子类中不可以重写
void foo2() override; // Error: 父类中foo2已经被final
auto foo3() -> int final; // OK:final关键词要放在尾指返回类型后
void bar() final; // Error: 父类中没有 bar虚函数可以被重写或final
};
从上面的程序可以看出,final需要加在特定位置才行,否则编译错误:
-
final必须要加在虚函数后面
-
在派生类中重写父类的没有显示写明final的虚函数后面
另外,如果函数有尾指返回类型,那么final关键词要放在尾指返回类型后。
final还可以直接用在类上,紧跟着类名后面,表示这个类禁止任何其他类继承它,无论是public
继承还是private
继承,如下:
class Base final {
// ...
};
class Derived : public Base { // ERROR,Base已经是final了,不允许继承
// ...
};
override
C++11 中的 override 关键字,可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。
若使用标识符
override
,则其紧随成员函数声明或类定义内的成员函数定义语法中的声明符之后出现。在成员函数声明或定义中,
override
说明符确保该函数为虚函数并覆盖某个基类中的虚函数。否则会编译错误。override 是在成员函数声明符之后使用时拥有特殊含义的标识符:其他情况下它不是保留的关键词。
struct Base {
virtual void foo();
void bar();
};
struct Derived : Base {
// 错误:Derived::foo 不覆盖 Base::foo, 签名不匹配
void foo() const override;
void foo() override; // OK:Derived::foo 覆盖 Base::foo
void bar() override; // 错误:Base::bar 非虚
};
int main() {
}
我们用一个示例场景说明一下:在子类中,本来你的意图是想覆写虚函数的,但是最后验证时发现,覆写的函数没有被正确地调用呢,或者更惨的是,你有时不得不去修改父类虚函数的声明。在所有的子类中查找重载的函数又很麻烦,而且墨菲定律告诉你:你永远会忘掉你搞错了的那一个子类。不管怎么样,看下边这个例子吧:
using namespace std;
class Base {
public:
virtual void function(int arg) const {
cout << "This is Base::function" << endl;
}
};
class Derived : public Base {
public:
virtual void function(long arg) const {
cout << "This is Derived::function" << endl;
}
};
int main() {
// Base pointer b points to a Derived class object.
shared_ptr b = make_shared
(); // Call virtual functionA through Base pointer.
b->function(99);
return 0;
}
在该程序中,Base 类指针 b 指向 Derived 类对象。因为 function 是一个虚函数,所以一般可以认为 b 对 function 的调用将选择 Derived 类的版本。
但是,从程序的输出结果来看,实际情况并非如此。其原因是这两个函数有不同的形参类型,所以 Derived 类中的 function 不能覆盖 Base 类中的 function。基类中的函数釆用的是 int 类型的参数,而派生类中的函数釆用的则是 long 类型的参数,因此,Derived 类中的 function 只不过是重载 Base 类中的 function 函数,这种情况导致两个类(基类和派生类)中的function函数都保留下来了。
其实你的本意是想要派生类中的function函数覆盖基类中的function函数,但是在派生类时错误定义了function函数,导致这两个函数签名不一致无法覆写。当然,要确认派生类中的成员函数覆盖基类中的虚成员函数,可以在派生类的函数原型(如果函数以内联方式写入,则在函数头)后面加上 override 关键字。override 关键字告诉编译器,该函数应覆盖基类中的函数。如果该函数实际上没有覆盖任何函数,像当前这种情况,参数类型不一致,如果在后面加上关键字override,则会导致编译器错误。
下面的程序演示了上面程序的修改方法,使得 Derived 类的函数可以真正覆盖 Base 类的函数。请注意,在该程序中已经将 Derived 类函数的形参修改为 int,并且在函数中添加了 override 关键字。这样才能输出我们想要的结果:
using namespace std;
class Base {
public:
virtual void function(int arg) const {
cout << "This is Base::function" << endl;
}
};
class Derived : public Base {
public:
virtual void function(int arg) const override {
cout << "This is Derived::function" << endl;
}
};
int main() {
// Base pointer b points to a Derived class object.
shared_ptr b = make_shared
(); // Call virtual functionA through Base pointer.
b->function(99);
return 0;
}
在派生类中,重写 (override) 继承自基类成员函数的实现 (implementation) 时,要满足如下条件:
-
基类中,成员函数声明为虚拟的 (virtual);
-
基类和派生类中,成员函数的返回类型和异常规格 (exception specification) 必须兼容;
-
基类和派生类中,成员函数名、形参类型、常量属性 (constness) 和 引用限定符 (reference qualifier) 必须完全相同;
如此多的限制条件,导致了虚函数重写如上述代码,极容易因为一个不小心而出错,在声明需要重写的函数后,加上关键字 override,这样,即使不小心漏写了虚函数重写的某个苛刻条件,也可以通过编译器的报错,快速改正错误。
总结
关于final
和override
关键字位置的一个小提示:这两者应放在const
,volatile
等其他关键字后边,但是应该在纯虚标记,也就是"=0
"的前边。一个final
的纯虚函数是没什么意义的,因为本身就是一个抽象函数又不让后边的子类覆写给与它实际意义。另外就是override final
和final override
并没有什么区别,只是后者读起来可能更顺一些吧。只写一个final
并不会像override
那样检查覆写类型,所以最好还是两个都写上。
而且,override
和final
都能帮助我们改善虚函数相关的安全性,使用final
需要更多的权衡但是override
就尽管大胆地用吧。
作者:Codemaxi
链接:https://juejin.cn/post/7114122592327483405
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原文始发于微信公众号(汇编语言):C++关键字:final和override用法
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论