下面这段代码定义了一个基类 Ref 和一个派生类 People,基类的析构函数被声明为 virtual

class Ref
{
public:
	virtual ~Ref()
	{
		cout << "~Ref()" << endl;
	}
};

class People :public Ref
{
public:
	~People()
	{
		cout << "~People()" << endl;
	}
};

栈对象的析构

先看第一种用法,People 以栈对象的形式出现,那么离开作用域时会发生什么?

int main(int argc, char *argv[])
{
	{
		People hk;
	}


	system("pause");
	return 0;
}

没错,先析构子类 People,然后再析构父类 Ref。在这种场景下,基类析构函数 ~Ref 加不加 virtual 都没有关系。

通过基类指针 delete 派生类对象

再来看下面这种多态用法,把一个 new People 赋给 Ref* 指针,然后通过基类指针 delete

int main(int argc, char *argv[])
{

		Ref *hk=new People;

		delete hk;

	system("pause");
	return 0;
}

这种写法也没问题——毕竟是多态,子类部分和基类部分都能被正确析构。

如果基类析构函数没有 virtual

问题来了:如果此时基类 Ref 的析构函数没有 virtual 关键字,会怎样?由于 hk 的静态类型是指向 Ref 的指针,delete 操作只会调用 Ref 的析构函数,子类 People 的部分根本不会被析构,后果就是内存泄漏——子类部分没有被清理,这一点很容易验证。

这也解释了为什么上一篇文章里 Refrelease 中的 delete this 能够同时删除子类和基类:对 Ref 类而言,this 就相当于上面代码里的 hk,虽然从表面上看并不像 Ref *hk=new People; 那样直观地暴露出多态,但基类 this 指向的有效地址其实依旧是 new People 分配出来的那块内存,只不过这块内存同时被基类 this 和子类 this 共享引用。

结论

所以一般情况下,析构函数应当尽量使用 virtual 关键字。虽然虚函数表会带来一些额外的系统开销,但在避免内存泄漏——尤其是需要内存自动管理的场景下,优点显而易见。