File tree Expand file tree Collapse file tree 1 file changed +64
-0
lines changed
Expand file tree Collapse file tree 1 file changed +64
-0
lines changed Original file line number Diff line number Diff line change 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`修饰的函数也会成为虚函数被进一步派生重写。
You can’t perform that action at this time.
0 commit comments