Effective C++ 条款07:为多态基类声明virtual析构函数

规则一 任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数

factory(工厂)函数

class TimeKeeper {
public:
	TimeKeeper();
	~TimeKeeper();
	...
};
class AtomicClock: public TimeKeeper { ... };
class WaterClock: public TimeKeeper { ... };
class WristWatch: public TimeKeeper { ... };

// factory 函数
TimeKeeper* getTimeKeeper();   // 返回一个指针,指向一个TimeKeeper派生类的动态分配对象
// 调用
TimeKeeper* ptk = getTimeKeeper();  // 从TimeKeeper继承体系获得一个动态分配对象,这也叫做动态绑定。
...									 // 运用它
delete ptk 							 // 释放它,避免资源泄漏

问题来了,因为TimeKeeper的析构函数不是virtual的,那么在delete的时候,也就只能删除掉父类TimeKeeper,而不会掉用子类的析构函数,从而导致了内存泄漏。

解决办法就是给base class一个virtual析构函数。

class TimeKeeper {
public:
	TimeKeeper();
	virtual ~TimeKeeper();
	...
};
TimeKeeper* ptk = getTimeKeeper();  
...									 
delete ptk   // 现在,行为正确

注意 这个时候掉用析构函数的顺序是先掉用子类(derived class)的析构函数,再掉用父类的析构函数(base class),这个和构造函数的掉用顺序恰恰相反。

规则二 如果class不含virtual函数,通常表示它并不意图被用做一个base class

当class不企图被当作base class,令其析构函数为virtual往往是个馊主意。
因为这样做会导致空间的浪费。
浪费空间还好说,更严重的是可能导致内存泄漏。

class SpecialString: public std::string {	// 馊主意!std::string有个non-virtual析构函数
	...
};
// 调用
SpecialString* pss = new SpecialString("Impending Doom");
std::string* ps;
...
ps = pss;			// SpecialString* => std::string*
...
delete ps;			// 未有定义!现实中*ps的SpecialString资源会泄漏,因为SpecialString析构函数没被调用。

相同的分析适用于任何不带virtua析构函数的class,包括所有STL容器如vector,list,set,trl::unordered_map等。

规则三 有时候令class带一个pure virtual析构函数,可能颇为便利。

纯虚函数会导致abstract classes,也就是不能被实体化的class,也就是说不能为这种类型创建对象。因此,想用的话,必须继承,继承又要考虑到析构的过程,所以我们考虑设置纯虚的析构函数就好了。
关于虚函数和纯虚函数的区别请参考之前的博客。

class AWOV {
public:
	virtual ~AWOV() = 0;
};
// 注意一定要定义纯虚析构函数,因为在析构的过程中,子类的析构函数会掉用父类的析构函数,所以必须定义,不然连接器会报错。
AWOV::~AWOV() {}		// 纯虚析构函数的定义

规则四 “给base class一个virtual析构函数” 这个规则只适用于带多态性质的基类上

因为这种基类的设计目的就在于动态绑定,父类对象调用子类实例。
并非所有的base class的设计目的都是为了多态用途。

总结

  1. 带多态性质的base class应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
  2. classes的设计目的如果不是作为base classes使用,或者不是为了具备多态性质,就不该声明virtual析构函数。
原文地址:https://www.cnblogs.com/zhonghuasong/p/7353501.html