规则一 任何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的设计目的都是为了多态用途。
总结
- 带多态性质的base class应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
- classes的设计目的如果不是作为base classes使用,或者不是为了具备多态性质,就不该声明virtual析构函数。