在类中,构造函数用于初始化对象及相关操作,构造函数是不能声明为虚函数的,因为在执行构造函数前对象尚未完成创建,虚函数表并不存在,此时就无法去查询虚函数表因此也就无法得知该调用哪一个构造函数了。
析构函数则用于销毁对象完成时相应的资源释放工作,析构函数可以被声明为虚函数。我们可以通过下面这个例子来了解一下基类析构函数声明为虚函数的必要性。
#include<iostream>
using namespace std;
class base
{
public:
base();
~base();
private:
int *a;
};
class derived:public base
{
public:
derived();
~dericed();
private:
int *p;
};
base ()
{
cout<<"base constructor"<<endl;
int *a = new int[10];
}
~base()
{
cout<<"base destructor"<<endl;
delete [] a;
}
derived()
{
cout<<"derived constructor"<<endl;
int *p = new int[1000];
}
~derived()
{
cout<<"derived destructor"<<endl;
delete [] p;
}
int main()
{
base *ptr = new derived();
delete ptr;
system("pause");
return 0;
}
运行结果
base constructor!
derived constructor!
base destructor!
可以分析一下上面的程序:
在这个例子里面定义了两个类,一个基类,一个是派生类,其中派生类和基类都分别定义了自己的构造函数和析构函数,基类和派生类中各有一个int型指针成员变量,在基类的构造函数中,给指针变量a分配了10个int型空间,在基类的析构函数则是用于将a所指的空间释放掉,在派生类的构造函数中,指针成员变量被分配了1000个整型空间,在派生类的析构函数则是为了释放掉b指针所指向的存储空间。在主函数中,我们创建一个基类类型的指针,指针指向一个派生类对象,之后释放掉p指针所指向的对象的存储空间。
观察程序的运行结果,程序打印出了“base constructor”这串字符串,则说明基类的构造函数被调用了,之后又打印出了”derived constructor”这串字符,同样的派生类的构造函数也被调用了。
当我们用new操作符创建一个派生类对象时会先调用基类构造函数,然后调用派生类构造函数
程序输入的结果与我们料想的是一样的,至此基类的成员变量a通过构造函数被分配了10个整型存储空间,派生类的成员b通过构造函数被分配了1000个整型存储空间,之后程序打印出了“base destructor”字符串,这说明基类的析构函数被调用了,a指针所指向的10个整型内存空间被释放了。
但是之后却并没有调用派生类的析构函数,不调用派生类的析构函数则会导致b指针所指向的1000整型存储空间不会被释放,如此一来造成了内存泄漏
为了解决这个问题,我们可以将基类的析构函数声明为虚函数,修改后基类的定义如下:
class base
{
public:
base();
vortual ~base();
private:
int *a;
};
改后程序的运行结果:
base constructor!
derived constructor!
derived destructor!
base destructor!
将基类的析构函数声明为虚函数之后,派生类的析构函数也自动成为虚析构函数,在主函数中基类指针p指向的是派生类对象,当delete释放p指针所指向的存储空间时,会执行派生类的析构函数,派生类的析构函数执行完之后会紧接着执行基类的析构函数,以释放从基类继承过来的成员变量所消耗的资源。