Q:C++中析构函数能不能是虚函数?
A:我是最近才注意到这个问题,首先是笔试的时候遇到过,当时根本不会,后来去查了一下,一知半解,大都说析构函数常常为虚函数,还有的人说必须是,后来面试的时候面试官正好问了,我也没讲太清楚,所以觉得有必要再重新梳理一下。
严谨的说,C++在有继承发生的时候,必须要把基类的析构函数写为虚函数,如果不这么做,在释放资源时,只会释放基类的资源,而不会释放子类的,造成内存泄漏。但是如果是没有继承,那就完全没必要把析构函数写为虚函数了,因为我们知道,对于有虚函数的类,C++都会为其分配一个虚函数表,这样做就会造成内存不必要的开销。
下面举例子来说明:
class Base
{
public:
Base()
{
cout << "Base的构造函数被执行" << endl;
}
~Base()
{
cout << "Base的析构函数被执行" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
cout << "Derived的构造函数被执行" << endl;
}
~Derived()
{
cout << "Derived的析构函数被执行" << endl;
}
};
int main()
{
Derived* d = new Derived;
delete d;
return 0;
}
可以看到,当用一个子类的指针去指向子类对象时,此时,不把基类析构写为虚函数也无妨。(子类和基类的析构函数都得到了调用)
但是当用父类的指针去指向子类对象时侯呢?看代码:
class Base
{
public:
Base()
{
cout << "Base的构造函数被执行" << endl;
}
~Base()
{
cout << "Base的析构函数被执行" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
cout << "Derived的构造函数被执行" << endl;
}
~Derived()
{
cout << "Derived的析构函数被执行" << endl;
}
};
int main()
{
Base* d = new Derived;
delete d;
return 0;
}
可以明显的看到,只有基类的析构函数被执行了,这样就会造成内存泄露,是不合理的。
总结:
其实之所以C++要这么做,原因很简单,就是因为在有多态技术的前提下,我们用父类的指针去指向子类对象时侯,其实是希望调用的子类的函数,但是我们如果事先并没有把该函数声明为虚函数的话,那最终调用的还只是父类的方法而已。如果我们希望调用子类的方法,那么就势必要把该方法在父类中声明为虚函数,这样做既析构了父类,又析构了子类,一箭双雕,何乐不为呢?
构造函数不能为虚函数
1. 存储空间角度:虚函数对应一个vtable,vtable存储于对象的内存空间,若构造函数是虚的,则需要通过 vtable来调用,若对象还未实例化,即内存空间还没有,无法找到vtable;
2. 使用角度:虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数就没有实际意义;
3. 从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有太大的必要成为虚函数。