考虑以下程序
class base {
public:
base() {
cout << "base constructor" << endl;
}
~base() {
cout << "base destructor" << endl;
}
};
class derived : public base {
public:
derived() {
cout << "derived constructor" << endl;
}
~derived() {
cout << "derived destructor" << endl;
}
};
int main()
{
base* b = new derived();
delete b;
while(true){}
return 0;
}
该段代码的输出为
也就是说,析构时并没有调用子类的析构函数,造成了一个诡异的“局部销毁”现象。
解决方法就是使基类的析构函数称为虚函数。
virtual ~base() {
cout << "base destructor" << endl;
}
这样,输出为
子类部分得到了正确的析构。
但是值得注意的是,c++并没有默认析构函数是虚函数。因为并不是所有的类都是为了被继承而生的,也就是说,它们不会被作为基类,那么也就不会出现以上“局部销毁”的情况,而此时析构函数若默认为虚函数,那么该类中会生成一个vptr(虚表指针),这个vptr的产生是毫无意义的(因为这个类根本不会用这个vptr),也就是说白白浪费了一个指针所占的内存空间,假设程序中有很多这样的类,那么浪费的内存空间就比较可观了。
许多人的心得是:当一个class内至少有一个virtual函数时,就将析构函数声明为虚函数。
通过以上,我们知道了一个基类,它的析构函数应当为虚析构函数,那么问题来了,有时我们并不会考虑这么周全,请看以下代码
class newString : public string {
public:
newString() {
cout << "newString constructor" << endl;
}
~newString() {
cout << "newString destructor" << endl;
}
};
int main()
{
string* a = new newString();
delete a;
while(true){}
return 0;
}
我们继承了一个“系统给的类” string,我们可能就会忘记以上的教训,在这种情况下,delete一个指向newString的string类型的指针就会产生“局部销毁”,导致内存泄漏。 不仅仅是string,换作包括STL的容器如vector,list, set等等,情况也是相同的。
所以如果你设计了一个类,并且这个类不打算作为基类被继承的,那么请声明其为final(c++11新特性)
class Nbase final{
};
还有一个注意事项是,如果将基类析构函数声明为虚析构函数,那么这个析构函数必须有定义,否则会报连接错误。
尤其要注意,如果把这个析构函数声明为纯虚函数(此时这个类为抽象类)
virtual ~derived() = 0;
那么就必须显式地在类外定义这个析构函数
base::~base(){}
不是说如果析构函数只是普通的虚函数而不是纯虚函数就不用提供定义,而是我们给普通的虚析构函数提供定义比较顺手罢了(在声明后面直接加{})。
而声明为纯虚函数的话容易忘记,假如在程序庞大的情况下,报一个连接错误我相信是很让人抓狂的,有时候并不会想到只是缺少了一个纯虚析构函数的定义而已。(所以还是要养成好习惯)