c++ 建構函式的異常是乙個比較難纏的問題,很多時候,我們可能不去考慮這些問題,如果被問到,有人可能會說使用raii管理資源。
但你真的考慮過如果建構函式失敗了,到底會發生什麼嗎,前面構造成功的成員、基類物件應該怎樣**?
最近在知乎上看到有人提到這個問題:
看了陳碩的回答,抱著懷疑的心態寫**加以驗證。
在此之前,先不急著上**,囉嗦幾句話。
首先問4個問題,這是從 herb sutter 的《more exceptrional c++》看到的,我覺得問的很好,類似保安的哲理問題:你是誰、你從**來、你要到**去?
1:物件生命週期何時開始
乙個建構函式成功執行完畢,並成功返回之時,也就是建構函式成功執行到函式體尾,沒有發生異常。
2:物件生命週期何時結束
當乙個對像的析構函式開始執行,也就是達到析構函式開始指出,這裡暫且不討論析構函式是否發生異常,只要進入析構函式體,該物件生命週期就已經結束
3:在生命週期開始之前,與生命結束之後,物件處於什麼狀態
這時候「物件」已不是物件。理論上「它」根本就不存在
4:接著第三個答案,如果建構函式異常,物件處於什麼狀態?
建構函式異常,即建構函式甚至沒有到達函式體的尾部,即物件的生命週期還沒有開始,所以他根本不是乙個的物件,或者說它什麼都不是,
所以更不會執行析構函式了。
那麼問題來了,如果構造失敗,之前成功分配的資源怎麼辦呢?
herb sutter的答案是:這個是語言本身來負責**了,編譯器來實現,沒程式設計師的事,即使之前成功構造的對像也不會執行析構函式。
下面是陳碩列舉建構函式失敗可能發生的場景,他舉了5個例子,我這裡寫了4個,我的結論如下
1:建構函式的初始化列表裡拋異常,前面已經構造好的成員由編譯器負責**,不會呼叫析構函式
2:陣列元素構造時拋異常,前面已經構造好的元素由編譯器**,不會呼叫物件的析構函式。
3:多繼承中某個基類的建構函式拋異常,已經構造成功的基類物件由編譯器**,不會呼叫析構函式
4:智慧型指標,stl 容器 存放auto_ptr, shared_ptr物件, 型別t構造失敗,則前面構成成功的智慧型物件有編譯器**,不會呼叫析構函式。
第一種:建構函式初始化列表丟擲異常,前面成功構造的對像由編譯器負責**,不會呼叫析構函式
1 #include2第十行出,我故意throw 33 using namespace std;
4 5 class b
12 b(int num)
16 ~b()
19 private:
20 int age;
21 };
22 23 class a
31 ~a()
37 private:
38 char *_data;
39 b b;
40 b *bp;
41 };
42 43 int main(void)
所以在25行class a建構函式的初始化列表,呼叫b(10)發生異常,而b()則會異常退出。結果如下
b的預設建構函式和b(int)都執行完畢,但析構函式沒有執行,當然a的構造也失敗了,更不會執行析構函式,這些資源都是編譯器負責**了
如果你不信的會,我注釋第9行,是另一種結果
第2種:陣列元素構造發生異常,前面構造成功的物件由編譯器負責**,不會呼叫析構函式
1 1 class c當隨機數r 整除 4是,即throw異常,則前面成功構造的物件不會析構11 11 }
12 12 ~c()
15 15 private:
16 16 int num;
17 17 };
18 18
19 19 int main(void)
若注釋第9行的throw r 結果是:
上面說的這點和下面這個觀點相悖,我試了一下發現不管異常是在建構函式中拋還是正常的呼叫中拋,要想讓系統呼叫已經構造好的物件,需要在外面try catch,否則讓std catch住是不會呼叫析構直接raise訊號了
棧展開級析構不應拋異常原因
在步入正題前,我們先來講講什麼叫棧展開(stack unwinding),才能更好理解c++異常(exception)的機制是怎樣運作的:
void f1() throw(int) //假設此析構函式可能會丟擲異常
};void dosomething() //在這一行呼叫了v的析構函式,資源被釋放
當v被呼叫析構函式,它包含的所有widget物件也都會被呼叫析構函式。又因為v是乙個容器,如果在釋放第乙個元素時觸發了異常,它也只能繼續釋放別的元素,否則會導致其它元素的資源洩露。如果在釋放第二個元素的時候又觸發了異常,那麼程式同樣會導致崩潰。
不僅僅是std::vector,所有stl容器的類甚至包括陣列也都會像這樣因為析構函式丟擲異常而崩潰程式,所以在c++中,不要讓析構函式丟擲異常!
more effective c++提出兩點理由(析構函式不能丟擲異常的理由):
1)如果析構函式丟擲異常,則異常點之後的程式不會執行,如果析構函式在異常點之後執行了某些必要的動作比如釋放某些資源,則這些動作不會執行,會造成諸如資源洩漏的問題。
2)通常異常發生時,c++的機制會呼叫已經構造物件的析構函式來釋放資源,此時若析構函式本身也丟擲異常,則前乙個異常尚未處理,又有新的異常,會造成程式崩潰的問題。
附:std::thread 收到的異常其實在std內部處理過一次,已經不是原始的異常資訊(沒有拋異常的棧位置這些);而boost::thread會原封不動的**原始異常棧!
構造,析構,異常處理
1.假設有class a,new乙個a的時候,在a的建構函式中丟擲異常,那麼此次new動態申請的記憶體會自動釋放掉麼?系統會選擇operator new 對應的operator delete來釋放記憶體,如果找不到對應的operator delete,則記憶體洩露。詳見effective c ite...
C 構造和析構
include using namespace std class b b b b b b int i data i b operator b b private int data b play b b int main output constructed by parameter 5 destr...
C 構造和析構
建構函式的作用是給類中的資料成員初始化,在乙個類物件產生的時候自動呼叫,建構函式分為帶參構造 無參構造。他倆唯一的區別就是乙個有引數,乙個沒引數,視實際情況使用 class cmyc cmyc cmyc cmyc cmyc int a,float b,char ch 它在乙個類物件被建立的時候自動呼...