68 C++的虚析构函数
前情提要:26 Destructors in C++,27 Inheritance in C++.28 Virtual Functions in C++
虚析构函数可以想象为虚函数和析构函数的组合。 虚析构函数对于处理多态非常重要,换句话说,如果我有一系列的子类和所有的继承:有一个类 A,然后一个类 B 派生于 A,你想把类 B 引用为类 A,但它实际上是类 B,然后你决定删除 A 或者它以某种方式删除了,然后你还是希望运行 B 的析构函数,而不是运行 A 的析构函数,这就是所谓的虚析构函数以及它的作用。
简单理解下就直接看代码吧:
#include <iostream>
class Base
{
public:
Base() { std::cout << "Base Constructor\n"; }
~Base() { std::cout << "Base Destructor\n"; }
};
class Derived : public Base
{
public:
Derived() { std::cout << "Derived Constructor\n"; }
~Derived() { std::cout << "Derived Destructor\n"; }
};
int main()
{
Base* base = new Base();
delete base;
std::cout << "---------------------\n";
Derived* derived = new Derived();
delete derived;
std::cin.get();
}
很可能已经猜到运行结果了: 创建和删除 Base 类时只会调用 Base 类的构造和析构函数; 对于 Derived 类,首先调用了基类的构造函数,然后是 Derived 类的构造函数,当我们删除时先调用 Derived 类的析构函数,再调用基类 Base 类的析构函数。
新加入一个多态类型:
std::cout << "---------------------\n";
Base* poly = new Derived(); // 创建一个Derived实例,但是把它赋值给Base类
delete poly;
这里只有基类的析构函数被调用了,而派生类的析构函数没有被调用。
这点很重要,因为这会造成内存泄漏。
delete
poly 时,它不知道这个调用的析构函数可能有另一个析构函数,因为它(~Base)没有被标记为虚函数。
标记为virtual,意味着 C++知道在层次结构下可能有某种重写的方法,这个方法就可以被覆写。 而virtual destructor(虚析构函数)的意思不是覆写析构函数,而是加上一个析构函数。换句话说如果我把积累的析构函数改为虚函数,它实际会先调用派生类析构函数,然后在层次结构中向上,调用基类析构函数。
举一个例子来说明为什么这会造成内存泄漏:
// 给派生类加一个数组成员
class Derived : public Base
{
public:
Derived() { m_Array = new int[5]; std::cout << "Derived Constructor\n"; }
~Derived() { delete[] m_Array; std::cout << "Derived Destructor\n"; }
private:
int* m_Array;
};
再回想一下刚才运行程序的结果,很明显这里并没有调用派生类的析构函数,因此给数组分配的 20 字节内存没有被释放,造成了内存泄漏。
那如何解决呢?
很简单,给基类的析构函数加上virtual
即可:
class Base
{
public:
Base() { std::cout << "Base Constructor\n"; }
virtual ~Base() { std::cout << "Base Destructor\n"; }
};
这意味着这个类有可能拓展出子类,可能还有一个析构函数也需要被调用:
很好,现在有了和第二个例子完全一样的结果,意味着即使我们把它当做多态类型处理(或者说是当做基类类型处理),基类和派生类的析构函数都调用了,那个数组也得到了清理。
所以当你在写一个要拓展的类或者子类时,只要你允许一个类拥有子类,你 100%需要声明你的析构函数为虚函数,否则无法安全地拓展这个类。