析构函数与 override
Contents
virtual
override
final
上面三个关键字在继承体系中起着重要作用。virtual
不用多说,声明虚函数必备的关键字。override
和final
是 C++11 中引入的,二者的作用其实更多是提醒开发者自己,
- 我正在重写一个虚函数
- 我正在重写一个虚函数,并且不希望再被派生类重写这个函数
当然,final
也可以用来修饰类,表示这是最后一次继承,即我这个类不能再被继承了。很形象,例如
|
|
但除此之外,final
和override
对编译器而言,确实有助益。例如,
- 如果你用
override
和final
修饰一个函数,但基类没有这个函数,或者这个函数在基类没有被声明为虚函数,就会引发编译错误。当然,你可能只是手滑打错字了。但确实,编译错误帮你很快定位到这个问题。 - 如果你用
override
和final
修饰一个函数,你就不必再用virtual
修饰。因为这两个修饰已经暗示这个函数是虚函数。当然,如果基类中声明已经声明了virtual
,无论你用不用这些修饰词,这个函数在派生类中都是虚函数。
然而,有一个很纠结的问题。在多态体系中,基类的析构函数必须是[[../notes/cpp/多态#When should my destructor be virtual
?|虚]]的。这就要问了,那对于派生类的析构函数,到底要不要声明为virtual
,又或者用override
或final
修饰呢?这个问题很让人纠结,从下面这个 issue 出发,你可以看到大家的讨论
用override
修饰析构函数
不过我自己的建议是,对于派生类的析构函数,总是用override
修饰。如果你这么做了,你将得到如下收益:
- 编译器确保基类的析构函数是虚的,否则编译报错。真的有人会忘记将基类析构函数声明为虚的。
- 你可以直观看出,当前这个类有一个虚析构函数。
同时,这也会因此困惑,
- 析构函数可以被重写吗?你不敢确信,于是上网搜寻相关资料,发现并不能重写析构函数。过了一个月,你看到这段代码,又重演一遍上述剧情!
确实,用override
修饰析构函数会造成困惑!但是,它利大于弊。事实上,虚析构函数中的“虚”和普通的虚函数有着不一样的语义。对于普通成员函数,让他成为虚函数的目的是,我摆明了想在派生类中重写它,进而达到多态的效果。而析构函数的虚,是一种机制上的必须。当你用基类指针或引用使用[[../notes/cpp/多态#Dynamic binding|动态绑定]]时,对象的销毁依赖于虚析构函数。如果基类的析构函数非虚,那么对象销毁时,只会调用基类的析构函数,这可能造成派生类的资源没有释放,进而导致内存泄漏。而如果基类的析构函数是虚的,那么会调用到派生类的析构函数,而派生类的析构函数保证会调用基类的析构函数(C++标准保证),这样一来,就能保证资源以合理的顺序释放。
所以,用override
修饰析构函数,并不是重写基类的析构函数(事实上我们也无法做到),而是在提醒编译器检查基类的虚构函数必须是虚的。
为什么不用final
修饰析构函数?
事实上,final
用来修饰函数,可以防止这个函数继续被派生类重写。但是,每个类都必须有析构函数!一旦析构函数被final
修饰,那么这个类将无法再被继承。因为继承这个类,默认会带上生成析构函数,无论用不用这三个关键字修饰。然而,这个行为被基类的final
阻止了,冲突了。这里,编译器认为是重写了被final
修饰的函数,编译无法通过。
为什么不用virtual
修饰析构函数?
可以,但没必要。因为如果基类析构函数已经是虚的,那么派生类析构函数自然而然也是虚的。用virtual
修饰不会增加任何编译检查,因为virtual
是声明虚函数的,对编译器没有任何提示作用。即便是基类没有的函数,派生类用virtual
修饰的函数也会成为虚函数被进一步派生重写。