八、 C++特性之override与final
我总觉得 C++中虚函数的设计很差劲,因为时至今日仍然没有一个强制的机制来标识虚函数会在派生类里被改写。vitual关键字是可选的,这使得阅读代码变得很费劲。因为可能需要追溯到继承体系的源头才能确定某个方法是否是虚函数。为了增加可读性,我总是在派生类里也写上virtual关键字,并且也鼓励大家都这么做。即使这样,仍然会产生一些微妙的错误。看下面这个例子:
1 class B 2 { 3 public: 4 virtual void f(short) {std::cout << "B::f" << std::endl;} 5 }; 6 7 class D : public B 8 { 9 public: 10 virtual void f(int) {std::cout << "D::f" << std::endl;} 11 };
D::f 按理应当重写 B::f。然而二者的声明是不同的,一个参数是short,另一个是int。因此D::f只是拥有同样名字 的另一个函数(重载)而不是重写。当你通过B类型的指针调用f()可能会期望打印出D::f,但实际上则会打出 B::f 。
另一个很微妙的错误情况:参数相同,但是基类的函数是const的,派生类的函数却不是。
1 class B 2 { 3 public: 4 virtual void f(int) const {std::cout << "B::f " << std::endl;} 5 }; 6 7 class D : public B 8 { 9 public: 10 virtual void f(int) {std::cout << "D::f" << std::endl;} 11 };
同样,这两个函数是重载而不是重写,所以你通过B类型指针调用f()将打印B::f,而不是D::f。
幸运的是,现在有一种方式能描述你的意图。新标准加入了两个新的标识符(不是关键字)::
- override,表示函数应当重写基类中的虚函数。
- final,表示派生类不应当重写这个虚函数。
第一个的例子如下:
1 class B 2 { 3 public: 4 virtual void f(short) {std::cout << "B::f" << std::endl;} 5 }; 6 7 class D : public B 8 { 9 public: 10 virtual void f(int) override {std::cout << "D::f" << std::endl;} 11 };
现在这将触发一个编译错误(后面那个例子,如果也写上override标识,会得到相同的错误提示):
- 'D::f' : method with override specifier 'override' did not override any base class methods
另一方面,如果你希望函数不要再被派生类进一步重写,你可以把它标识为final。可以在基类或任何派生类中使用final。在派生类中,可以同时使用override和final标识。
1 class B 2 { 3 public: 4 virtual void f(int) {std::cout << "B::f" << std::endl;} 5 }; 6 7 class D : public B 8 { 9 public: 10 virtual void f(int) override final {std::cout << "D::f" << std::endl;} 11 }; 12 13 class F : public D 14 { 15 public: 16 virtual void f(int) override {std::cout << "F::f" << std::endl;} 17 };
被标记成final的函数将不能再被F::f重写。