class TimeKeeper { public: TimeKeeper(); ~TimeKeeper(); ... };
class AtomicClock: public TimeKeeper { ... }; class WaterClock: public TimeKeeper { ... }; class WristWatch: public TimeKeeper { ... }; 很多客户只是想简单地取得时间而不关心如何计算的细节,所以一个 factory 函数——返回一个指向新建派生类对象的基类指针的函数——被用来返回一个指向计时对象的指针:
TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic- // ally allocated object of a class // derived from TimeKeeper 按照 factory 函数的惯例,getTimeKeeper 返回的对象是建立在堆上的,所以为了避免泄漏内存和其他资源,最重要的就是要让每一个返回的对象都可以被完全删除。
TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object // from TimeKeeper hierarchy
... // use it
delete ptk; // release it to avoid resource leak 现在我们精力集中于上面的代码中一个更基本的缺陷:即使客户做对了每一件事,也无法预知程序将如何运转。
class Point { // a 2D point public: Point(int xCoord, int yCoord); ~Point(); PRivate: int x, y; }; 假如一个 int 占 32 位,一个 Point 对象正好适用于 64 位的寄存器。而且,这样一个 Point 对象可以被作为一个 64 位的量传递给其它语言写的函数,比如 C 或者 FORTRAN。假如 Point 的析构函数是虚拟的,情况就完全不一样了。
class SpecialString: public std::string { // bad idea! std::string has a ... // non-virtual destrUCtor }; 一眼看上去,这可能无伤大雅,但是,假如在程序的某个地方因为某种原因,你将一个指向 SpecialString 的指针转型为一个指向 string 的指针,然后你将 delete 施加于这个 string 指针,你就马上被送入未定义行为的领地。
SpecialString *pss = new SpecialString("Impending Doom");
std::string *ps; ... ps = pss; // SpecialString* => std::string* ... delete ps; // undefined! In practice, // *ps’s SpecialString resources // will be leaked, because the // SpecialString destructor won’t // be called. 同样的分析可以适用于任何缺少虚析构函数的类,包括全部的 STL 容器类型(例如,vector,list,set,tr1::unordered_map。假如你受到从标准容器类或任何其他带有非虚析构函数的类派生的诱惑,一定要挺住!(不幸的是,C++ 不提供类似 java 的 final 类或 C# 的 sealed 类的防派生气制。) 偶然地,给一个类提供一个纯虚析构函数能提供一些便利。回想一下,纯虚函数导致抽象类——不能被实例化的类(也就是说你不能创建这个类型的对象)。有时候,你有一个类,你希望它是抽象的,但没有任何纯虚函数。怎么办呢?因为一个抽象类注定要被用作基类,又因为一个基类应该有一个虚析构函数,又因为一个纯虚函数产生一个抽象类,好了,解决方案很简单:在你希望成为抽象类的类中声明一个纯虚析构函数。这是一个例子: