Skip to content

Commit 7a379c7

Browse files
committed
new article: override destructor
1 parent e0673a2 commit 7a379c7

File tree

1 file changed

+64
-0
lines changed

1 file changed

+64
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
---
2+
title: "析构函数与 override"
3+
date: 2025-09-03T21:40:03+08:00
4+
keywords: []
5+
categories: []
6+
tags: [cpp,virtual,override]
7+
mathjax: false
8+
9+
---
10+
11+
- `virtual`
12+
- `override`
13+
- `final`
14+
15+
上面三个关键字在继承体系中起着重要作用。`virtual`不用多说,声明虚函数必备的关键字。`override``final`是 C++11 中引入的,二者的作用其实更多是提醒开发者自己,
16+
17+
- 我正在重写一个虚函数
18+
- 我正在重写一个虚函数,并且不希望再被派生类重写这个函数
19+
20+
<!--more-->
21+
22+
当然,`final`也可以用来修饰类,表示这是最后一次继承,即我这个类不能再被继承了。很形象,例如
23+
24+
```cpp
25+
class A {};
26+
class B : public A {};
27+
class C final : pubilc B {};
28+
class D : public C {}; // compile error!
29+
```
30+
31+
但除此之外,`final`和`override`对编译器而言,确实有助益。例如,
32+
33+
- 如果你用`override`和`final`修饰一个函数,但基类没有这个函数,或者这个函数在基类没有被声明为虚函数,就会引发编译错误。当然,你可能只是手滑打错字了。但确实,编译错误帮你很快定位到这个问题。
34+
- 如果你用`override`和`final`修饰一个函数,你就不必再用`virtual`修饰。因为这两个修饰已经暗示这个函数是虚函数。当然,如果基类中声明已经声明了`virtual`,无论你用不用这些修饰词,这个函数在派生类中都是虚函数。
35+
36+
然而,有一个很纠结的问题。在多态体系中,基类的析构函数必须是[[../notes/cpp/多态#When should my destructor be `virtual`?|虚]]的。这就要问了,那对于派生类的析构函数,到底要不要声明为`virtual`,又或者用`override`或`final`修饰呢?这个问题很让人纠结,从下面这个 issue 出发,你可以看到大家的讨论
37+
38+
- [C.128: Should destructors be marked "override"?](https://github.com/isocpp/CppCoreGuidelines/issues/721)
39+
40+
用`override`修饰析构函数
41+
---
42+
43+
不过我自己的建议是,**对于派生类的析构函数,总是用`override`修饰**。如果你这么做了,你将得到如下收益:
44+
45+
1. 编译器确保基类的析构函数是虚的,否则编译报错。真的有人会忘记将基类析构函数声明为虚的。
46+
2. 你可以直观看出,当前这个类有一个虚析构函数。
47+
48+
同时,这也会因此困惑,
49+
50+
1. 析构函数可以被重写吗?你不敢确信,于是上网搜寻相关资料,发现并不能重写析构函数。过了一个月,你看到这段代码,又重演一遍上述剧情!
51+
52+
确实,*用`override`修饰析构函数会造成困惑*!但是,它利大于弊。事实上,虚析构函数中的“虚”和普通的虚函数有着不一样的语义。对于普通成员函数,让他成为虚函数的目的是,我摆明了想在派生类中重写它,进而达到多态的效果。而析构函数的虚,是一种机制上的必须。当你用基类指针或引用使用[[../notes/cpp/多态#Dynamic binding|动态绑定]]时,对象的销毁依赖于虚析构函数。如果基类的析构函数非虚,那么对象销毁时,只会调用基类的析构函数,这可能造成派生类的资源没有释放,进而导致内存泄漏。而如果基类的析构函数是虚的,那么会调用到派生类的析构函数,而派生类的析构函数保证会调用基类的析构函数(C++标准保证),这样一来,就能保证资源以合理的顺序释放。
53+
54+
所以,用`override`修饰析构函数,并不是重写基类的析构函数(事实上我们也无法做到),而是在提醒编译器检查基类的虚构函数必须是虚的。
55+
56+
为什么不用`final`修饰析构函数?
57+
---
58+
59+
事实上,`final`用来修饰函数,可以防止这个函数继续被派生类重写。但是,每个类都必须有析构函数!一旦析构函数被`final`修饰,那么这个类将无法再被继承。因为继承这个类,默认会带上生成析构函数,无论用不用这三个关键字修饰。然而,这个行为被基类的`final`阻止了,冲突了。这里,编译器认为是重写了被`final`修饰的函数,编译无法通过。
60+
61+
为什么不用`virtual`修饰析构函数?
62+
---
63+
64+
可以,但没必要。因为如果基类析构函数已经是虚的,那么派生类析构函数自然而然也是虚的。用`virtual`修饰不会增加任何编译检查,因为`virtual`是声明虚函数的,对编译器没有任何提示作用。即便是基类没有的函数,派生类用`virtual`修饰的函数也会成为虚函数被进一步派生重写。

0 commit comments

Comments
 (0)