如果構造函數內發生異常,已經分配的資源是不會自動釋放的,比如
~ std::runtime_error(~~= = ~** main( argc, **
運行結果如下:
into A constructor
into D constructor
into B constructor
into C constructor
terminate called after throwing an instance of 'std::runtime_error'
what(): exception from C constructor
對象c在構造過程中拋出異常,指針b指向的內存空間不會被釋放。
如何釋放b的內存呢?首先我們可以看出c++是不會調用析構函數的,因為析構函數不知道對象已經構造了多少,哪些資源該釋放,哪些不該釋放,當然編譯器可以記錄這些內容,但是會嚴重影響效率。另外在語義上,c++認為,對象的生命周期是構造函數正常結束至析構函數結束之間,構造不完全的對象是一個沒有生命的東西,是不存在的,你不能對一個不存在的對象調用析構函數。編譯器默認會做的只是釋放對象d的內存空間。對象b指向的堆內存可以通過使用異常顯示釋放
= = (std::runtime_error &==}
運行結果如下:
into A constructor
into D constructor
into B constructor
into C constructor
into D constructor catch
into B destructor
into D destructor
into A destructor
b被正常釋放了,我們再做一下改變,將b和c的構造放到初始化列表中做,將D的構造函數改成下面這樣,
D::D() : A(),b( B()),c(
我們繼續使用異常,會不會有效?
D() :A(), b( B()), c((std::runtime_error &
看上去very nice啊,跑一下試試,
into A constructor
into B constructor
into C constructor
into A destructor
in D constructor catch: exception from C constructor
into B destructor
into C destructor
*** glibc detected *** ./a.out: free(): invalid pointer: ***
指針錯誤!同時我們可以發現在進入catch語句前基類A執行了析構函數,這說明到達catch語句時,已經跳出了構造函數的范圍,D和A的成員數據都已經是不可訪問的了。
C++為什麼要這樣做呢,為什麼構造函數體外的catch語句中無法訪問數據成員?
首先,無法知道b和c是否已經初始化了,刪除未初始化指針是非法的。
其次,我們假設可以知道b初始化了,c沒有初始化,我們要刪除b,如果catch中可以訪問b和c的話(我們做個假設),改變B的類型,使B包含一個A*成員數據,考慮下這種情況,
D() :A(), b( B(static_cast<A*>())), c((std::runtime_error &
A已經析構了,b的數據成員A已經不存在,這是很危險的。
c++認為,不管是基類還是數據成員構造出現失敗,那麼整個對象構造都是失敗的,不存在半成品。構造函數塊外的catch語句(上面這種)即使沒有顯示地重新拋出異常,c++也會自動拋出,一直到最下層派生類對象構造處停住。比如以上例子,catch沒有顯示throw,在main函數裡:
(std::runtime_error &
仍然會捕捉到C構造函數拋出的runtime_error異常。
綜上,可以總結以下規則
1. 構造函數體外的try-catch語句只有一個用途——轉換捕捉到的異常對象
2. 必須在構造函數體內分配資源,不要在初始化列表中
3. 一定要用try-catch語句管理資源的話,在構造函數體內。
4. 使用RAII方式管理資源,這會少死亡很多腦細胞,像這樣子
<B><C> B()), c(
歡迎討論
參考
http://www.gotw.ca/gotw/066.htm
《more effective c++》