假设我们有一个基类A,很不幸的,A的析构函数是一个non-virtual。同时我们有一个派生类B,它派生自A。
我们定义了一个A类型指针,它指向的实际对象是B:
A * ptr = new B;
然后在程序的某处,我们不再需要B了,我们将指针ptr delete掉:
delete ptr;
注意new永远要和delete成队出现,除非你使用智能指针。这时由于A的析构函数是non-virtual,一个悲剧诞生:
C++中明确指出,当派生类对象经由一个基类指针被删除,而该基类带着一个non-virtual析构函数,其结果未定义--实际执行时通常发生的是对象的派生部分没有被销毁。也就是B对象部分发生内存泄漏。
问题的解决办法就是为多态基类A声明一个虚析构函数。简单吧,另一个更加通用的准则是:如果一个类拥有至少一个虚函数,那么这个类也应该要有一个虚析构函数,因为类中拥有虚函数已经表明该类将作为基类来使用,期待子类实现自己的虚函数,此时基类应该为析构函数作virtual声明。
反过来的另一条准则是,如果一个类不想作为基类,那么不要为其声明任何成员函数做virtual声明。为啥?virtual有代价啊。为了支持多态机制,编译器为类对象安插一个虚表指针,同时类也多了一张虚函数表。如果我们的类本来只有一个int类型的数据成员,在32bit机器上,sizeof(类)为4,而加了vptr后,sizeof(类)为8,类对象的大小整整翻了100%!
当然,就算我们的编程遵循了上面的准则,有时候还有有些许错误。切勿不要以没有任何虚函数的类作为基类。有时候由于我们的粗心大意或者在毫不知情的情况下我们可能会干这种事情。例如,我们想实现一个自己的容器MyList,并想借助STL的list来实现:
class MyList :public list
{
//...
};
那么内存泄漏的问题又来了,STL中的list并不打算作为一个基类来使用,因此它的析构函数也不是一个virtual。类似的还有string、stl的vector、set、unordered_map等等。
好在,C++11 提供了final关键字,对于不想被继承的类,我们在其定义时加上final关键字:
class Super final
{
//......
};
这样试图继承super时编译器将会报错。