条款7:为多态基类声明virtual析构函数

多态基类增加一个virtual的析构函数

现在需要一种设计,要设计一个类记录时间,但是记录时间的方式有很多,可以通过手机MobileClock,可以通过水钟WaterClock等。

所以要这种实现的方式:

class TimeKeeper
{
public:
	TimeKeeper();
	~TimeKeeper();
};

class MobileClock : public TimeKeeper
{
public:
	MobileClock();
	~MobileClock();
};

class WaterClock : public TimeKeeper
{
public:
	WaterClock();
	~WaterClock();
};

  

这种设计的方式是工厂factory的方式,利用了C++多态的方式。利用父类的指针TimeKeeper*指向任何子类的对象就可以实现了,这样的方式很容易维护和功能的扩展。比如有一天我想扩展计时的方式,我想用原子钟计时,增加一个AtomicClock类,利用这种设计能改动最少的代码实现功能的扩展。

所以工厂函数是这样的:

TimeKeeper *ptk = getTimeKeeper();

  被getTimeKeeper返回的这个对象应该位于内存的heap存储区,为了避免内存泄露,程序必须将factory返回的每一个对象在合适的时候用delete释放掉。

所以代码是这样的:

TimeKeeper *ptk = getTimeKeeper();
	
//TODO:这里用ptk做一些操作,比如显示时间等

delete ptk;

  这样的操作表面上看似乎有点合理,但是我们会发现在通过释放delete对象的时候,基类指针ptk要调用一个析构函数。又由于基类(TimeKeeper)的析构函数是non-virtual的,是静态绑定的。所以这里通过ptk指针调用的是基类TimeKeeper的析构函数。那么这个基类的析构函数能把整个派生类对象释放掉吗?答案是不能。它只能释放派生类对象中的基类的成分,并不能释放专属于派生类的那部分空间。于是就造成了一个诡异的“局部销毁”对象。

解决这个问题的方法很简单:把基类的析构函数做成virtual的,这样的话通过基类的指针ptk调用一个析构函数的时候,就会动态绑定,根据ptk实际指向的对象调用这个对象的析构函数。这样就自动的调用到了派生类的析构函数。通过派生类的析构函数就能如愿的释放掉派生类对象,同时也能够释放掉基类的成分

class TimeKeeper
{
public:
	TimeKeeper();
	virtual ~TimeKeeper();
};

  注意这里说的为析构函数增加virtual关键字针对的是多态基类而言的。这种类都是用来派生用的,同时还会有其他的virtual函数,如virtual getCurrentTime,然后在不同的派生类中在定制它们自己的getCurrentTime。所以总而言之,任何用作多态基类的class都会有一些virtual的函数,这时为这个多态基类定义一个virtual的析构函数是很必要的。

多态基类和virtual函数是一起出现的,一个类只要有virtual函数,那么这个类一定是要打算作为多态基类使用的,所以要有一个virtual的析构函数。换而言之,如果一个类是作为多态基类设计的,那么它至少有一个virtual函数,那么这时析构函数也要声明为virtual。

所以这一条的规则是为多态基类声明一个virtual的析构函数。但是反过来,如果一个库文件中的类并没有设计成基类的意图,千万不要去继承它,不然当你对这个基类使用多态的时候,就会发生上面的“对象局部销毁”的现象发生。(因为你不能修改一个库文件中的类)

非多态基类的类禁止virtual的析构函数

考虑一个二维空间的坐标点的class:

class Point
{
public:
	Point(int xCoord, int yCoord);
	~Point();
private:
	int x,y;
};

  这样的类在设计的时候都没有把他当做基类的意图,你如果硬是要给它的析构函数加上一个virtual,那么这个class的实例就会占用更多的空间,因为除了两个int变量外,还有在对象的开始的部分存放该对象的虚函数表的指针,表明这个对象的虚函数表在内存的地址。这样的话如果把这个类给C语言或者其他语言,大小就会不对应,除非明确的补偿虚函数表指针占用的空间,才能具有移植性。

所以,对于那些非多态基类的类,不要把其析构函数声明为virtual的。

非多态的基类也要禁止virtual析构函数

并非所有的基类的设计目的都是为了多态用途,例如标准string和STL容器都不被设计作为基类使用,更别提多态了。某些class的设计目的是作为基类使用,但不是为了多态,它们并不需要“父类的指针指向子类的对象”做一些操作。所以也不需要声明virtual的析构函数。

 

总结

上面的规则其实很简单:

为多态基类声明一个virtual的析构函数。

如果一个类不是用作基类的用途,不要把它的析构函数声明为virtual;

如果一个类是作为基类的用途,同时需要“父类的指针指向子类的对象”做一些操作,那么要声明一个virtual的析构函数;

如果一个类是作为基类的用途,但是不需要多态操作子类对象,不要声明一个virtual的析构函数。

最后一句:多态基类和virtual函数是一起出现的,谁离开了谁都玩不了。

  

你可能感兴趣的:(virtual)