下面这段代码定义了一个基类 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 的部分根本不会被析构,后果就是内存泄漏——子类部分没有被清理,这一点很容易验证。
这也解释了为什么上一篇文章里 Ref 类 release 中的 delete this 能够同时删除子类和基类:对 Ref 类而言,this 就相当于上面代码里的 hk,虽然从表面上看并不像 Ref *hk=new People; 那样直观地暴露出多态,但基类 this 指向的有效地址其实依旧是 new People 分配出来的那块内存,只不过这块内存同时被基类 this 和子类 this 共享引用。
结论
所以一般情况下,析构函数应当尽量使用 virtual 关键字。虽然虚函数表会带来一些额外的系统开销,但在避免内存泄漏——尤其是需要内存自动管理的场景下,优点显而易见。