C++
借由虚函数实现运⾏时多态,但
C++
的虚函数⼜很多脆弱的地⽅:
- ⽆法禁⽌⼦类重写它。可能到某⼀层级时,我们不希望⼦类继续来重写当前虚函数了。
- 容易不⼩⼼隐藏⽗类的虚函数。⽐如在重写时,不⼩⼼声明了⼀个签名不⼀致但有同样名称的新函数
C++11
提供了
final 来禁⽌虚函数被重写/禁⽌类被继承, override 来显示地重写虚函数。 这样编译器给我们不⼩⼼的⾏为提供更多有⽤的错误和警告。
final:
C++11的关键字final有两个用途。第一,它阻止了从类继承;第二,阻止一个虚函数的重载。
struct Base1 final { };
struct Derived1 : Base1 {}; // 编译错:Base1不允许被继承
struct Base2 {
virtual void f1() final;
};
struct Derived2 : Base2 {
virtual void f1(); // 编译错:f1不允许重写
};
override:
关于重载虚函数的两个常见错误如下:
1 无意中重载
首先,我们来分析一下无意中重载的综合症。你可能只是通过声明了一个与基类的某个虚成员函数具有相同的名字和签名的成员函数而无意中重载了这个虚函数。编译器和读代码的人很难发现这个bug因为他们通常以为这个新函数是为了实现对基类函数的重载:
class A
{
public:
virtual void func();
};
class B: A{};
class F{};
class D: A, F
{
public:
void func(); // meant to declare a new function but accidentally overrides A::func
};
阅读以上代码,你不能确定成员函数D::func()是否故意重载了A::func().它也可能是个偶然发生的重载,因为两个函数的参数列表和名字都碰巧一样。
2 签名不匹配
签名不匹配是一个更为常见的情景。这导致意外创建一个新的虚函数(而不是重载一个已存在的虚函数),正如以下例子所示:
class G
{
public:
virtual void func(int);
};
class H: G
{
public:
virtual void func(double);
};
这种情况下,程序员本打算在类H中重载G::func()的。然而,由于H::func()拥有不同的签名,结果创建了一个新的虚函数,而非对基类函数的重载
碰到这种情况,不是所有的编译器都会给个警告,有时那样做会被设置成抑制这种警告。
基于上面的两个错误,在C++11中,通过使用新关键字override可以消除这两个bugs。override明确地表示一个函数是对基类中一个虚函数的重载。更重要的是,它会检查基类虚函数和派生类中重载函数的签名不匹配问题。如果签名不匹配,编译器会发出错误信息。
在成员函数声明或定义中, override 确保该函数为虚函数并覆写来自基类的虚函数。
struct Base2 {
virtual void f2();
};
struct Derived2 : Base2 {
virtual void f2(int) override; // 编译错:⽗类中没有 void f2(int)
};