- C++服务器开发精髓
- 张远龙
- 1424字
- 2025-02-17 13:58:12
1.6 final、override关键字和=default、=delete语法
final、override、=default、=delete是C++11新增的一组非常具有标记意义的关键字和语法,这里逐一介绍它们。
1.6.1 final关键字
final 关键字修饰一个类,这个类将不允许被继承,这在其他语言(如 Java)中早就实现了。在 C++11中,final关键字要写在类名的后面,这在其他语言中是写在 class关键字前面的。示例如下:


由于类A被声明成final,B继承A,所以编译器会报如下错误提示类A不能被继承:
error C3246:'B':cannot inherit from'A'as it has been declared as'final'
1.6.2 override关键字
C++语法规定,在父类中加了virtual关键字的方法可以被子类重写,子类重写该方法时可以加或不加virtual关键字,例如下面这样:

这种宽松的规定可能会带来以下两个问题。
◎ 当我们阅读代码时,无论子类重写的方法是否添加了virtual关键字,我们都无法直观地确定该方法是否是重写的父类方法。
◎ 如果我们在子类中不小心写错了需要重写的方法的函数签名(可能是参数类型、个数或返回值类型),这个方法就会变成一个独立的方法,这可能会违背我们重写父类某个方法的初衷,而编译器在编译时并不会检查到这个错误。
为了解决以上两个问题,C++11引进了override关键字,其实override关键字并不是新语法,在 Java 等其他编程语言中早就支持。类方法被 override 关键字修饰,表明该方法重写了父类的同名方法,加了该关键字后,编译器会在编译阶段做相应的检查,如果其父类不存在相同签名格式的类方法,编译器就会给出相应的错误提示。
情形一,父类不存在,子类标记了override的方法:

由于在父类A中没有func方法,所以编译器会提示错误:

情形二,父类存在,子类标记了override的方法,但函数签名不一致:

这时编译器会报同样的错误。
正确的代码如下:

1.6.3=default语法
如果一个 C++类没有显式给出构造函数、析构函数、拷贝构造函数、operator=这几类函数的实现,则在需要它们时,编译器会自动生成;或者,在给出这些函数的声明时,如果没有给出其实现,则编译器在链接时会报错。如果使用=default标记这类函数,则编译器会给出默认的实现。来看一个例子:

这样的代码是可以编译通过的,因为编译器默认生成 A 的一个无参构造函数,假设我们现在向A提供一个有参构造函数:

这时,编译器就不会自动生成默认的无参构造函数了,这段代码会编译出错,提示A没有合适的无参构造函数:

我们这时可以手动为A加上无参构造函数,也可以使用=default语法强行让编译器自己生成:


=default 最大的作用可能是在开发中简化了构造函数中没有实际初始化代码的写法,尤其是声明和实现分别属于.h和.cpp文件。例如,对于类 A,其头文件为 a.h,其实现文件为 a.cpp,则正常情况下我们需要在 a.cpp 文件中写其构造函数和析构函数的实现(可能没有实际的构造和析构逻辑):

可以发现,即使在 A 的构造函数和析构函数中什么逻辑也没有,我们还是不得不在a.cpp中写上构造函数和析构函数的实现。有了=default关键字,我们就可以在a.h中直接写成:

1.6.4=delete语法
既然有强制让编译器生成构造函数、析构函数、拷贝构造函数、operator=的语法,那么也应该有禁止编译器生成这些函数的语法,没错,就是=delete。
在C++98/03规范中,如果我们想让一个类不能被拷贝(即不能调用其拷贝构造函数),则可以将其拷贝构造函数和operator=函数定义成private的:

通过以上代码利用a1构造a2时,编译器会提示错误:

我们利用这种方式间接实现了一个类不能被拷贝的功能,这也是继承自boost::noncopyable 的类不能被拷贝的实现原理。现在有了=delete 语法,我们直接使用该语法禁止编译器生成这两个函数即可:

一般在一些工具类中,我们不需要用到构造函数、析构函数、拷贝构造函数、operator=这4个函数,为了防止编译器自己生成,同时为了减小生成的可执行文件的体积,建议使用=delete语法禁止编译器为这4个函数生成默认的实现代码,例如:
