先上结论:
1、带有多态性质的基类,应该声明一个 virtual 析构函数。如果类带有任何 virtual 函数,它就应该有一个 virtual 析构函数。
2、类的设计目的如果不是作为 base class 使用,或不是为了具备多态性,就不该声明 virtual 析构函数。
举一个工厂模式的例子:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();
};
class AtomicClock : public TimeKeeper
{
//原子钟
};
class WristWatch : public TimeKeeper
{
//腕表
};
先定义一个基类 TimeKeeper 和它的派生类 AtomicClock 和 WristWatch,设计一个工厂函数(工厂模式),返回指针指向一个计时对象。
TimeKeeper* getTimeKeeper()
{
TimeKeeper* t_keeper = NULL;
switch(type)
{
case 1:
t_keeper = new AtomicClock();
break;
case 2:
t_keeper = new WristWatch();
break;
default:
break;
}
return t_keeper;
}
在该函数中,我们 new 了一个指针,为了防止内存泄露,在使用完毕的时候一定要delete:
“依赖客户端执行 delete 动作,基本上便带有某种错误的倾向”!
如何理解这句话呢?getTimeKeeper 函数返回的指针类型是基类的,但指针却是指向一个派生类的对象,并最终要经由一个基类指针被删除,基类的析构函数是 non-virtual 的,这就导致在实际的执行中,对象中基类的部分被析构,但是派送类独有的部分却没有!这种情况被称为“局部销毁”对象,很容易造成内存泄露。
所以我们应该将基类的析构函数声明为 virtual 函数: virtual ~
TimeKeeper(); 确保在调用析构函数的时候,子类的析构函数可以“覆盖”父类的析构函数,从而完整的析构整个对象。
但是,如果一个
类,在设计的时候并不是为了作为 base class 来使用,或着说不是为了具备多态性,那么就不该将析构函数声明为 virtual 函数(这种情况下其他的成员函数也都不应该被声明为 virtual 函数)。
这是从代码的可移植性角度总结的做法。
class Point {
public:
Point();
~Point();
private:
int32_t a, b;
};
这样一个类,它的一个对象可以放入一个64位的寄存器,或者作为一个“64位长度的量”传递。
但如果这个类有一个 virtual 函数,那么它的对象必须携带更多的信息,用来在运行时期决定哪一个 virtual 函数该被调用。为了保存这些信息,我们需要一张虚表(vtbl)来保存函数的指针,相应的,类 Point 里要增加一个虚表指针 vptr 来指向这张虚表。这样的话,类需要的空间就是 96bits,那么在传递类的对象的时候,就不能把它当做“64bits的量”,要新增很多细节的实现。因此不再具有移植性。
所以,如果一个类不需要具备多态性,那么就不要为它声明 virtual 函数。