1. 程式人生 > >6 More Effective C++—條款9(區域性變數的destructor防止記憶體洩漏)

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指標的時候,丟擲異常。