析構函式中丟擲的異常

2021-04-13 14:09:31 字數 3825 閱讀 1876

析構函式在什麼時候被呼叫執行?

對於c++程式設計師來說,這個問題比較簡單,但是比較愛嘮叨的阿愚還是建議應該在此再提一提,也算回顧一下c++的知識,而且這將對後面的討論和理解由一定幫助。先看乙個簡單的示例吧!如下:

class mytest_base

};void main()

catch(...)

}編譯執行上面的程式,從程式的執行結果將會表明物件的析構函式被執行了,但什麼時候被執行的呢?按c++標準中規定,物件應該在離開它的作用域時被呼叫執行。實際上各個廠商的c++編譯器也都滿足這個要求,拿vc來做個測試驗證吧!,下面列出的是剛剛上面的那個小示例程式在除錯時拷貝出的相關程式片段。注意其中obj物件將會在離開try block時被編譯器插入一段**,隱式地來呼叫物件的析構函式。如下:

325: try

326: // 瞧下面,編譯器插入一段**,隱式地來呼叫物件的析構函式

00401320 lea ecx,[obj]

00401323 call @ilt+15(mytest_base::~mytest_base) (00401014)

331: catch(...)

00401328 jmp __tryend$_main$1 (00401365)

332:

0040135f mov eax,offset __tryend$_main$1 (00401365)

00401364 ret

335: }

析構函式中丟擲的異常

1、仍然是先看示例,如下:

class mytest_base

void func() throw()

void other() {}

void main()

catch(std::exception e)

catch(...)

}程式執行的結果是:

開始準備銷毀乙個mytest_base型別的物件

在析構函式中故意丟擲乙個異常,測試!

從上面的程式執行結果來看,並沒有什麼特別的,在程式中首先是構造乙個物件,當這個物件在離開它的作用域時,析構函式被呼叫,此時析構函式中丟擲乙個std::exception型別的異常,因此後面的catch(std::exception e)塊捕獲住這個異常,並列印出異常錯誤資訊。這個過程好像顯現出,發生在析構函式中的異常與其它地方發生的異常(如物件的成員函式中)並沒有什麼太大的不同,除了析構函式是隱式呼叫的以外,但這也絲毫不會影響到異常處理的機制呀!那究竟區別何在?玄機何在呢?繼續往下看吧!

2、在上面的程式基礎上做點小的改動,程式**如下:

void main()

catch(std::exception e)

catch(...)

}注意,修改後的程式現在的執行結果:非常的不幸,程式在控制台上列印一條語句後就崩潰了(如果程式是debug版本,會顯示一條程式將被終止的斷言;如果是release版本,程式會被執行terminate()函式後退出)。在主人公阿愚的機器上執行的debug版本的程式結果如下:

許多朋友對這種結果也許會覺得傻了眼,這簡直是莫名奇妙嗎?這是誰的錯呀!難道是新新增的那條**的問題,但這完全不會呀!(其實很多程式設計師朋友受到過太多這種類似的冤枉,例如乙個程式原來執行挺好的,以後進行功能擴充後,程式卻時常出現崩潰現象。其實有時程式擴充時也沒新增多少**,而且相關程式設計師也很認真仔細檢查自己新增的**,確認後來新增的**確實沒什麼問題呀!可相關的負責人也許不這麼認為,覺得程式以前一直執行挺好的,經過你這一番修改之後就出錯了,能不是你新增的**所導致的問題嗎?真是程式開發領域的竇娥冤呀!其實這種推理完全是沒有根據和理由的,客觀公正一點地說,程式的崩潰與後來新增的模組**肯定是會有一定的相關性!但真正的bug也許就在原來的系統中一直存在,只不過以前一直沒誘發表現出來而已!)

那究竟是什麼地方的問題呢?其實這實際上由於析構函式中丟擲的異常所導致的,但這就更詫異了,析構函式中丟擲的異常是沒有問題的呀!剛才的乙個例子不是已經測試過了嗎?是的,但那只是一種假象。如果要想使你的系統可靠、安全、長時間執行無故障,你在進行程式的異常處理設計和編碼過程中,至少要保證一點,那就是析構函式中是不永許丟擲異常的,而且在c++標準中也特別宣告到了這一點,但它並沒有闡述真正的原因。那麼到底是為什麼呢?為什麼c++標準就規定析構函式中不能丟擲異常?這確實是乙個非常棘手的問題,很難闡述得十分清楚。

c++異常處理模型是為c++語言量身設計的,更進一步的說,它實際上也是為c++語言中物件導向而服務的,我們在前面的文章中多次不厭其煩的宣告到,c++異常處理模型最大的特點和優勢就是對c++中的物件導向提供了最強大的無縫支援。好的,既然如此!那麼如果物件在執行期間出現了異常,c++異常處理模型有責任清除那些由於出現異常所導致的已經失效了的物件(也即物件超出了它原來的作用域),並釋放物件原來所分配的資源,這就是呼叫這些物件的析構函式來完成釋放資源的任務,所以從這個意義上說,析構函式已經變成了異常處理的一部分。不知大家是否明白了這段話所蘊含的真正內在涵義沒有,那就是上面的論述c++異常處理模型它其實是有乙個前提假設——析構函式中是不應該再有異常丟擲的。試想!如果物件出了異常,現在異常處理模組為了維護系統物件資料的一致性,避免資源洩漏,有責任釋放這個物件的資源,呼叫物件的析構函式,可現在假如析構過程又再出現異常,那麼請問由誰來保證這個物件的資源釋放呢?而且這新出現的異常又由誰來處理呢?不要忘記前面的乙個異常目前都還沒有處理結束,因此這就陷入了乙個矛盾之中,或者說無限的遞迴巢狀之中。所以c++標準就做出了這種假設,當然這種假設也是完全合理的,在物件的構造過程中,或許由於系統資源有限而致使物件需要的資源無法得到滿足,從而導致異常的出現,但析構函式完全是可以做得到避免異常的發生,畢竟你是在釋放資源呀!好比你在與公司續簽合同的時候向公司申**薪,也許公司由於種種其它原因而無法滿足你的要求;但如果你主動申請不要薪水完全義務工作,公司能不樂意地答應你嗎?

假如無法保證在析構函式中不發生異常,怎麼辦?

雖然c++標準中假定了析構函式中不應該,也不永許丟擲異常的。但有過的實際的軟體開發的程式設計師朋友們中也許會體會到,c++標準中的這種假定完全是站著講話不覺得腰痛,實際的軟體系統開發中是很難保證到這一點的。所有的析構函式的執行過程完全不發生一點異常,這根本就是天方夜譚,或者說自己欺騙自己算了。而且大家是否還有過這種體會,有時候發現析構乙個物件(釋放資源)比構造乙個物件還更容易發生異常,例如乙個表示引用記數的控制代碼不小心出錯,結果導致資源重複釋放而發生異常,當然這種錯誤大多時候是由於程式設計師所設計的演算法在邏輯上有些小問題所導致的,但不要忘記現在的系統非常複雜,不可能保證所有的程式設計師寫出的程式完全沒有bug。因此杜絕在析構函式中決不發生任何異常的這種保證確實是有點理想化了。那麼當無法保證在析構函式中不發生異常時,該怎麼辦?我們不能眼睜睜地看著系統崩潰呀!

其實還是有很好辦法來解決的。那就是把異常完全封裝在析構函式內部,決不讓異常丟擲函式之外。這是一種非常簡單,也非常有效的方法。按這種方法把上面的程式再做一點改動,那麼程式將避免了崩潰的厄運。如下:

class mytest_base

catch(…) {}

void func() throw()

void other() {}

};程式執行的結果如下:

開始準備銷毀乙個mytest_base型別的物件

故意丟擲乙個異常,測試!

怎麼樣,現在是不是一切都已經風平浪靜了。

析構函式中丟擲異常時概括性總結

(1) c++中析構函式的執行不應該丟擲異常;

(2) 假如析構函式中丟擲了異常,那麼你的系統將變得非常危險,也許很長時間什麼錯誤也不會發生;但也許你的系統有時就會莫名奇妙地崩潰而退出了,而且什麼跡象也沒有,崩得你滿地找牙也很難發現問題究竟出現在什麼地方;

(3) 當在某乙個析構函式中會有一些可能(哪怕是一點點可能)發生異常時,那麼就必須要把這種可能發生的異常完全封裝在析構函式內部,決不能讓它丟擲函式之外(這招簡直是絕殺!呵呵!);

(4)一定要切記上面這幾條總結,析構函式中丟擲異常導致程式不明原因的崩潰是許多系統的致命內傷!

析構函式丟擲異常

看了下effective c 關於析構函式丟擲異常的一些描述。然後自己網上查了下。發現一篇說的不錯的。轉了。具體出處不知道是 1.丟擲異常 1.1 丟擲異常 也稱為拋棄異常 即檢測是否產生異常,在c 中,其採用throw語句來實現,如果檢測到產生異常,則丟擲異常。該語句的格式為 throw 表示式 ...

建構函式和析構函式中丟擲異常

不會造成記憶體洩漏 1 new乙個物件有兩個過程 a.向系統申請記憶體空間 b.在申請的記憶體空間上執行建構函式,初始化物件。2 內部物件構造先於物件本身。3 物件在建構函式丟擲異常後,系統會負責清理構造物件時申請的記憶體,但不會呼叫物件析構函式。也就是說構造物件的記憶體會被釋放掉,已經完成例項化的...

為什麼析構函式中不能丟擲異常?

c 異常處理模型是為c 語言量身設計的,更進一步的說,它實際上也是為c 語言中物件導向而服務的。c 異常處理模型最大的特點和優勢就是對c 中的物件導向提供了最強大的無縫支援。那麼如果物件在執行期間出現了異常,c 異常處理模型有責任清除那些由於出現異常所導致的已經失效了的物件 也即物件超出了它原來的作...