为何基类析构函数必须为虚函数

基类析构函数为虚函数的必要性

由于 C++ 中类的多态性,使用基类指针指向派生类时,若函数是虚的,则调用派生类中的函数。析构函数也是如此。如果有个基类指针指向了派生类对象,删除这个指针,调用的则是派生类的析构函数。派生类的析构函数又会自动调用基类的析构函数,达到所有空间都释放的目的。

如果不声明为虚函数,那么编译器实行静态绑定,在删除基类指针时,只调用基类的虚构函数而不调用派生类的析构函数,造成派生类中有空间没有释放完全而导致内存泄漏

所以声明为虚函数是必要的,为了防止只析构基类而不析构派生类。

非虚析构函数会发生什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;
class Parent {
public:
Parent() {
cout << "父类构造函数" << endl;
}
~Parent() {
cout << "父类析构函数" << endl;
}
};

class Son : public Parent {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() {
cout << "子类析构函数" << endl;
}
};

int main() {
Parent *p = new Son();
delete p;
p = nullptr;
return 0;
}

结果:

1
2
3
4
@└────> # ./a.out 
父类构造函数
子类构造函数
父类析构函数

可以看到,子类析构函数并没有被调用到。

基类析构函数定义为虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;
class Parent {
public:
Parent() {
cout << "父类构造函数" << endl;
}
virtual ~Parent() {
cout << "父类析构函数" << endl;
}
};

class Son : public Parent {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() {
cout << "子类析构函数" << endl;
}
};

int main() {
Parent *p = new Son();
delete p;
p = nullptr;
return 0;
}

结果:

1
2
3
4
5
@└────> # ./a.out 
父类构造函数
子类构造函数
子类析构函数
父类析构函数

小技巧

如果父类的设计者忘记了在析构函数处加 virtual,子类的设计者有义务提醒父类设计者,避免内存泄漏。

C++11 的关键字 override 可以起作用。override 表示函数应当重写基类中的虚函数(用于派生类的虚函数中)。编译器会检查基类中的虚函数和派生类中带有 override 的虚函数有没有相同的函数签名,一旦不匹配便会报错。

因此子类设计者可以在其析构函数后增加关键字 override,一旦父类缺少关键字 virtual,就会被编译器发现并报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;
class Parent {
public:
Parent() {
cout << "父类构造函数" << endl;
}
~Parent() { // 此处没加 virtual,会有编译错误
cout << "父类析构函数" << endl;
}
};

class Son : public Parent {
public:
Son() {
cout << "子类构造函数" << endl;
}
~Son() override {
cout << "子类析构函数" << endl;
}
};

int main() {
Parent *p = new Son();
delete p;
p = nullptr;
return 0;
}

编译结果:

1
2
3
4
@└────> # g++ test.cc 
test.cc:18:9: error: ‘Son::~Son()’ marked ‘override’, but does not override
~Son() override {
^