6 More Effective C++—條款9(區域性變數的destructor防止記憶體洩漏)
0 生活雞湯
偶然看到一篇文章,每天前進一點點,積累下來,人生就能有所改變。已經有一段時間沒有更新這個系列,今天爭取再往前走一點點。
1 提出問題
寵物醫院提供收養服務,其中,主要收養物件是小狗(Dog)小貓(Cat)。收養需要走一定流程,具體流程我們不必關心。上面的情景可用下面程式碼描述。
class Animal { public: virtual void processAdoption() = 0; } class Cat : public Animal { public: virtual void processAdoption(); } class Dog : public Animal { public: virtual void processAdoption(); } void batchAdoption(istream& dataSource) { while (dataSource) { Animal *animal = readAnimal(dataSource); animal->processAdoption(); delete animal; } }
但是,如果上面的batchAdoption()方法中,readAnimal(), processAdoption(), 都可能丟擲異常, 程式中斷,從而導致delete animal無法執行,記憶體洩漏發生。
2 解決途徑
由於“防止記憶體洩漏”時本書的一個重要主題,為了一步步揭示思維過程,和書中內容保持一致,下面將給出逐步優化過程。
1 利用“異常捕獲”
考慮到上面readAnimal()和processAdoption()都有可能出現異常,因此可以將兩個語句放入try中。
這樣的程式碼比較冗餘,因此我們是否可以進一步將delete操作集中到一處進行處理?
void batchAdoption(istream& dataSource) { while (dataSource) { Animal *animal = readAnimal(dataSource); // 不能放入try中,否則animal對外部不可見 try { animal->processAdoption(); } catch (...) { delete animal; throw; } delete animal; } }
2 將指標用物件抱起來
我們可以將readAnimal()返回的指標,作為構造引數,放入一個類物件中;將delete animal的動作,放到類物件的解構函式中。此時,一旦退出類物件所在作用域,其解構函式被呼叫,那麼delete animal就會被執行。
STL提供了auto_ptr模板類來實現上面的設計。其可能的實現如下。
template <class T> class auto_ptr { public: auto_ptr(T *p = 0) : m_ptr(p) {} ~auto_ptr() { delete m_ptr;} private: T *m_ptr; };
這樣,上面的函式可以實現為下面的程式碼。
void batchAdoption(istream& dataSource) {
while (dataSource) {
auto_ptr animal(readAnimal(dataSource));
animal->processAdoption();
// 無需呼叫語句delete animal,出了作用域即呼叫解構函式
}
}
3 進一步應用
至此,我們的核心觀念已經提出:
1,利用“作用域”和“生存週期”來控制heap中物件的生存週期。 2,進一步,利用“作用域”和“生存週期”,來控制函式區域性內的行為。
根據上面的道理,進一步應用到現實場景。在視窗顯示資訊的過程中,出現異常,則視窗指標w將無法被銷燬。因此,我們可以用類物件將w封裝,從而保證無論什麼情況下,w總能被銷燬。
class WindowHandle {
public:
WindowHandle(Window_Handle handle) : w(handle){}
~WindowHandle() {destroyWindow(w);}
operator Window_Handle() { return w;} //隱式型別轉換函式
private:
Window_Handle w;
WindowHandle(const WindowHandle&); // 遮蔽複製建構函式
WindowHandle& operator=(const WindowHandle& ); // 遮蔽複製建構函式
4 提出新問題
後面條款10和條款11將分別討論如下兩個問題:
1,當初始化包裝heap指標的時候,丟擲異常。 2,當析構包裝heap指標的時候,丟擲異常。