基类析构函数为虚函数的必要性
由于 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() { 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 { ^
|