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 類的防派生氣制。) 偶然地,給一個類提供一個純虛析構函數能提供一些便利。回想一下,純虛函數導致抽象類——不能被實例化的類(也就是說你不能創建這個類型的對象)。有時候,你有一個類,你希望它是抽象的,但沒有任何純虛函數。怎么辦呢?因為一個抽象類注定要被用作基類,又因為一個基類應該有一個虛析構函數,又因為一個純虛函數產生一個抽象類,好了,解決方案很簡單:在你希望成為抽象類的類中聲明一個純虛析構函數。這是一個例子: