在C++中,构造函数用于在创建对象时进行初始化工作,不能声明为虚函数。因为在执行构造函数前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪一个构造函数。下节会讲解虚函数表的概念。
析构函数则用于在销毁对象时完成相应的资源释放工作,可以被声明为虚函数。
为了说明虚析构函数的必要性,请大家先看下面一个例子:
#include <iostream>
using namespace std;
//基类
class Base{
private:
int *a;
public:
Base();
~Base(){ cout<<"Base destructor"<<endl; }
};
Base::Base(){
a = new int[];
cout<<"Base constructor"<<endl;
}
//派生类
class Derived: public Base{
private:
int *b;
public:
Derived();
~Derived( ){ cout<<"Derived destructor"<<endl; }
};
Derived::Derived(){
b = new int[];
cout<<"Derived constructor"<<endl;
}
int main( ){
Base *p = new Derived;
delete p;
return ;
}
本例中定义了两个类,基类 Base 和派生类 Derived,它们都有自己的构造函数和析构函数。在构造函数中,会分配100个 int 型的内存空间;在析构函数中,会把这些内存释放掉。
在 main 函数中,定义了基类类型的指针 p,并指向派生类对象,然后希望用 delete 释放 p 所指向的空间
从运行结果可以看出,执行delete p;
语句时只调用了基类的析构函数,却没有调用派生类的析构函数。这会导致 b 所指向的 100 个 int 型内存空间得不到释放,除非程序运行结束被操作系统回收,否则就再也没有机会释放这些内存。这是典型的内存泄露。
内存泄露问题是程序员需要极力避免的。本例中出现的内存泄露是由于派生类的析构函数未被调用引起的,为了解决这个问题,需要将基类的析构函数声明为虚函数。修正后的代码如下所示:
class Base{
private:
int *a;
public:
Base();
virtual ~Base(){ cout<<"Base destructor"<<endl; }
};
Base::Base(){
a = new int[];
cout<<"Base constructor"<<endl;
}
如此,派生类的析构函数也会自动成为虚析构函数。当执行delete p;
语句时,会先执行派生类的析构函数,再执行基类的析构函数,这样就不存在内存泄露问题了。
这个例子足以说明虚析构函数的必要性,但是如果不管三七二十一的将所有的基类的析构函数都声明为虚函数,也是不合适的。通常来说,如果基类中存在一个指向动态分配内存的成员变量,并且基类的析构函数中定义了释放该动态分配内存的代码,那么就应该将基类的析构函数声明为虚函数。