30多年来,C++一直没有继承控制关键字。最起码这是不容易的,禁止一个类的进一步衍生是可能的但也很棘手。为避免用户在派生类中重载一个虚函数,你不得不向后考虑。
C++ 11添加了两个继承控制关键字:override和final
override:确保在派生类中声明的重载函数跟基类的虚函数有相同的签名。
final:阻止类的进一步派生和虚函数的进一步重载。接下来让我们看看这些监督者如何消除你在类层次结构的设计和实施中的bug吧。
虚函数重载
一个派生类可以重载基类中声明的成员函数,这是面向对象设计的基础。然而像重载一个函数这么简单的操作也会出错。关于重载虚函数的两个常见错误如下:
- 无意中重载
- 签名不匹配
首先,我们来分析一下无意中重载的综合症。你可能只是通过声明一个与基类的某个虚成员函数具有相同的名字和签名的成员函数而无意中重载了这个虚函数。编译器和读代码的人很难发现这个bug因为他们通常以为这个新函数是为了实现对基类函数的重载:
struct A { virtual void func(); }; struct B: A{}; struct F{}; struct D: A, F { void func(); //定义一个新函数,不小心重写了基类的虚函数A::func };
阅读以上代码,你不能确定成员函数D::func()是否故意重载了A::func().它也可能是个偶然发生的重载,因为两个函数的参数列表和名字都碰巧一样。
签名不匹配是一个更为常见的情景。这导致意外创建一个新的虚函数(而不是重载一个已存在的虚函数),正如以下例子所示:
struct G { virtual void func(int); }; struct H: G { virtual void func(double); //不小心创建了一个新的虚函数 };
H *p=new H; p->func(5); // calls G::f p->func(5.0); // calls H::f
碰到这种情况,不是所有的编译器都会给个警告,有时那样做会被设置成抑制这种警告。在C++11中,通过使用新关键字override可以消除这两个bugs。
override明确地表示一个函数是对基类中一个虚函数的重载,它会检查基类虚函数和派生类中重载函数的签名,如果签名不匹配,编译器会发出错误信息。
- 相同的函数名
- 在声明该函数的第一个基类中的一个虚拟说明符
- 基类函数和派生类重载函数具有相同的参数列表、返回类型(一种情况例外)、cv资格等。
如果这三个条件中任意一个不满足,编译器就会报错(在我们的例子中:G::func()需要int型而H::func()要求double的)。要是没有关键字override,编译器会简单的认为程序员想要在H中新建一个虚函数。
如果派生类函数确实是要重载基类的一个函数,它应该包含一个显式的override说明符。否则,假定D::func()是一个新的虚函数(这时,加一个注释是值得赞赏的),或者这也许是个bug。
final函数和类
C++11的关键字final有两个用途:
(1).它阻止了从类继承。
(2).阻止一个虚函数的重载。
某些实现系统服务、基础功能和加密等的类通常是不允许有子类的;实现者不想客户端从这些类派生新类而修改他们。标准库容器,如std:: vector和std:: list的无子类化类型就是另一个很好的例子,这些容器没有虚拟析构函数或者确切地说没有任何虚成员函数。
然而,程序员常常在没有意识到风险的情况下坚持从std::vector派生。在C++11中,无子类类型将被声明为如下所示:
class TaskManager {/*..*/} final; class PrioritizedTaskManager: public TaskManager { }; //compilation error: base class TaskManager is final
struct A { virtual void func() const; }; struct B: A { void func() const override final; //OK }; struct C: B { void func()const; //error, B::func is final };
语法和术语
迄今为止,我已经避免了两个有关override和final的次要问题。第一个是它们独特的位置。与virtual、inline、explicit extern以及一些类似的函数说明符不同的是,这两个关键字放在函数参数列表右括号之后,或者(对于无子类的类来说)一个类声明的右大括号之后。
这些关键字的特殊位置是由另一个不同寻常的性质决定的:override和final不是普通关键字,事实上官方地说,它们根本不是关键字。C++11把它们作为只是为了在特定上下文和位置下获取特殊意义的标示符。在任何其他位置或上下文,它们都被当成标示符。因此,一个完全有效的C++11代码如下:
//valid C++11 code int final = 0; bool override = false; if (override==true){ cout<<"override is:"<<override<<endl;} struct D{} final; struct A{ virtual bool func(); }; struct B:A { bool func() override final; };
那为什么委员会将final和override另外处理呢?选择CSK只是一种妥协方案。将override和final作为保留关键字可能对现有C++代码造成破坏。如果委员会已经引入了新的保留关键字,他们可能会选择像final_declor_Override这样时髦的,且不太可能与传统C++代码中用户声明的标示符相冲突的字符串等。然而,没有谁喜欢这么丑的关键字(比如,问问C使用者对C99的s_Bool的看法)。这是为什么最终采用CSK方法的原因。
override和final在C++11中作为关键字,但只在特定的上下文使用,不然它们只被当成普通标示符。