条款07:为多态基类声明虚析构函数
看下面一个类:
class TimeKeeper{
public :
TimeKeeper();
~TimeKeeper();
....
};
class AtomicClock : public TimeKeeper{...};
class WaterClock : public TimeKeeper{...};
class WristClock : public TimeKeeper{...};
当我们使用时,我们可以采用一个工厂方法,用基类的指针指向派生类的对象:
TimeKeeper * getTimeKeeper();
TimeKeeper * ptk = getTimeKeeper(); //用基类的指针指向派生类的对象
....
delete ptk;
(注,在以后,我会说明这种依赖用户来delete的方法不是一个好方法。但是现在不是我们所要讨论的问题。我们要讨论的问题是:。。。。)
我们要讨论的是,当我们把ptk进行析构时,会发生什么事情?
由于ptk是一个基类的指针,当进行析构时,会调用基类的析构函数,也就是说会把ptk所指对象的基类部分进行,但是ptk实际指向了一个派生类的对象,对于派生类的数据怎么办呢?此时会产生不明确行为,这是造成内存泄露的一个绝佳方式。
如果我们把基类的析构函数做成虚函数,则这个问题就可以解决了:
class TimeKeeper{
public :
TimeKeeper();
virtual ~TimeKeeper();
....
};
class AtomicClock : public TimeKeeper{...};
class WaterClock : public TimeKeeper{...};
class WristClock : public TimeKeeper{...};
由于基类的析构函数是一个虚函数,则每一个派生类中的析构函数都是虚函数。当我们再次使用一个基类指针指向派生类的时候,如下:
TimeKeeper * ptk = getTimeKeeper();
....
delete ptk;
再次进行析构时,由于析构函数是一个虚函数,所以在析构时,会在运行期来决定调用哪个类的析构函数,此时,经判断是派生类的析构函数,所以会调用派生类的析构函数,此时,可以把ptk所指向的对象全部析构。
结论:任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。
如果一个class不含virtual函数,通常表示它并不意图被用做一个base class,即使被当作基类,也不希望用基类的指针来指向一个派生类的对象。当class不企图被当作base class,令其析构函数为virtual,往往不是一个好主意。如下类:
class Point{
public :
Point(int xCoord, int yCoord) : x(xCoord),y(yCoord){}
~Point();
private:
int x, y;
}
这样一个类的对象,会占用64个字节的内存,如果其析构函数是一个虚函数,则此类的对象,将会是96字节,这样,浪费了50%的空间。
所以许多人的心得是:只有当class内含至少一个virtual函数,才为它声明一个virtual析构函数。
请记住:
- Polymorphic base class 应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
- Class的设计目的如果不是作为base class 使用,或不是为了具备多态性,就不应该声明virtual析构函数。s