Jiahonzheng's Blog

C++ 11 override 和 final

字数统计: 1k阅读时长: 3 min
2018/04/16 Share

C++ 11 之前,语言规范一直没有继承控制关键字,禁用一个类的进一步继承是可能的,也是棘手的。

C++ 11 添加了两个继承控制关键字 overridefinal ,让继承和成员函数覆盖更加规范和安全,在编译器做了保证。

override

override 关键字是为了在编译期之间,检查是否存在虚函数重载的常见错误。

override 可以明确地表示一个函数是对基类的一个虚函数的重载。注意,即使我们不用 override 关键字,可以在派生类中重载基类同名的虚函数

关于重载虚函数的两个常见错误如下:

  • 无意中重载
  • 签名不匹配

无意中重载

有时,我们可能只是通过声明了一个与基类的某个虚成员函数具有相同名字和函数列表的成员函数而无意中重载了这个函数。在这种情况下,编译器和读代码的人将会很难发现这个 bug ,因为他们通常以为这个新函数是为了实现对基类虚成员函数的重载。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class A {
public:
virtual void func();
};
class B : A {};
class F {};
class D : A, F {
public:
// 可能开发者只是声明了一个与基类的某个虚成员函数具有相同的名称和参数列表,而无意中重载
void func() {}

// override 明确表示该函数是对基类的虚成员函数的重载
void func() override{};
};

在上述代码中,我们不能确定成员函数 D::func() 是否故意重载了 A::func() ,它也可能是个偶然发生的重载

函数签名不匹配

在重载基类的虚成员函数时,我们可能因为重载的函数签名与基类的虚成员函数签名不匹配,导致意外地创建一个新的虚函数,而不是重载一个已存在的虚函数

这里,我们复习一下重载虚成员函数的注意点。

  • 重写的函数和被重写的函数必须为 virtual 函数,分别位于基类和派生类中。
  • 重写的函数和被重写的函数的名称和参数列表必须一致。
  • 重写的函数和被重写的函数返回值相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的类型是基类中被替换的虚函数所返回的指针或引用的类型的子类型。
  • 重写的函数的访问修饰符可以不同于被重写的函数,如基类的 virtual 函数的修饰符为 private ,派生类重写的函数的访问修饰符可以改为 publicprotected
  • 重写的函数可以带 virtual 关键字,也可以不带。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A{};
class B: public A{};

class Base {
public:
virtual A* func(int);
};

class Derived: public Base {
public:
// 由于参数列表不一致,导致新成员函数的意外产生
B* func(float);

// ERROR: 加入 override 关键字后,编译器会比较派生类中重载函数的名称和函数签名
B* func(float) override;
};

在上述代码中,我们本来打算在 H 中重载 G::func() ,然而由于 H::func() 拥有不同于 G::func() 的参数列表,结果创建了新的虚函数,而非对基类虚成员函数的重载。

final

C++ 11 的关键字 final 有两个用途:

  • 阻止类的进一步继承
  • 阻止虚函数在派生类中的重载。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A {
public:
// OK: final 只能修饰虚函数
virtual void func() final {}

// ERROR: final 只能修饰虚函数
void funcTest() final {}
};

// OK: final 可阻止类的进一步继承
class B final : public A {
public:
// ERROR: 不能重写已用 final 修饰的虚函数
void func() override {}
};

// ERROR: B 已用 final 修饰,即 B 不能被继承
class C : public B {};
CATALOG
  1. 1. override
    1. 1.1. 无意中重载
    2. 1.2. 函数签名不匹配
  2. 2. final