C++ 構造函數的異常是一個比較難纏的問題,很多時候,我們可能不去考慮這些問題,如果被問到,有人可能會說使用RAII管理資源。
但你真的考慮過如果構造函數失敗了,到底會發生什么嗎,前面構造成功的成員、基類對象應該怎樣回收?
最近在知乎上看到有人提到這個問題:
http://www.zhihu.com/question/22889420
看了陳碩的回答,抱着懷疑的心態寫代碼加以驗證。
在此之前,先不急着上代碼,啰嗦幾句話。
首先問4個問題,這是從 Herb sutter 的《More Exceptrional C++》看到的,我覺得問的很好,類似保安的哲理問題:你是誰、你從哪里來、你要到哪里去?
1:對象生命周期何時開始
一個構造函數成功執行完畢,並成功返回之時,也就是構造函數成功執行到函數體尾,沒有發生異常。
2:對象生命周期何時結束
當一個對像的析構函數開始執行,也就是達到析構函數開始指出,這里暫且不討論析構函數是否發生異常,只要進入析構函數體,該對象生命周期就已經結束
3:在生命周期開始之前,與生命結束之后,對象處於什么狀態
這時候“對象”已不是對象。理論上“它”根本就不存在
4:接着第三個答案,如果構造函數異常,對象處於什么狀態?
構造函數異常,即構造函數甚至沒有到達函數體的尾部,即對象的生命周期還沒有開始,所以他根本不是一個的對象,或者說它什么都不是,
所以更不會執行析構函數了。
那么問題來了,如果構造失敗,之前成功分配的資源怎么辦呢?
Herb sutter的答案是:這個是語言本身來負責回收了,編譯器來實現,沒程序員的事,即使之前成功構造的對像也不會執行析構函數。
下面是陳碩列舉構造函數失敗可能發生的場景,他舉了5個例子,我這里寫了4個,我的結論如下
1:構造函數的初始化列表里拋異常,前面已經構造好的成員由編譯器負責回收,不會調用析構函數
2:數組元素構造時拋異常,前面已經構造好的元素由編譯器回收,不會調用對象的析構函數。
3:多繼承中某個基類的構造函數拋異常,已經構造成功的基類對象由編譯器回收,不會調用析構函數
4:智能指針,STL 容器 存放auto_ptr<T>, shared_ptr<T> 對象, 類型T構造失敗,則前面構成成功的智能對象有編譯器回收,不會調用析構函數。
第一種:構造函數初始化列表拋出異常,前面成功構造的對像由編譯器負責回收,不會調用析構函數
1 #include<iostream> 2 3 using namespace std; 4 5 class B{ 6 7 public: 8 B(){ 9 cout << "construct B default" << endl; 10 throw 3; //故意在默認構造函數中拋出異常 11 } 12 B(int num){ 13 age = num; 14 cout << "constructor B ,age =" << num << endl; 15 } 16 ~B(){ 17 cout << "destructor B ,age=" << age << endl; 18 } 19 private: 20 int age; 21 }; 22 23 class A{ 24 public: 25 A():_data(new char[1]), b(B(10)), bp(new B()){ 26 cout << "construct A " << endl; 27 28 *_data = '\0'; 29 30 } 31 ~A(){ 32 cout << "destructor A" << endl; 33 delete [] _data; 34 delete bp; 35 36 } 37 private: 38 char *_data; 39 B b; 40 B *bp; 41 }; 42 43 int main(void){ 44 45 A a; 46 }
第十行出,我故意throw 3
所以在25行class A構造函數的初始化列表,調用B(10)發生異常,而B()則會異常退出。結果如下
B的默認構造函數和B(int)都執行完畢,但析構函數沒有執行,當然A的構造也失敗了,更不會執行析構函數,這些資源都是編譯器負責回收了
如果你不信的會,我注釋第9行,是另一種結果
第2種:數組元素構造發生異常,前面構造成功的對象由編譯器負責回收,不會調用析構函數
1 1 class C{ 2 2 public: 3 3 C(){ 4 4 //得到指定范圍[m,n]的隨機數:r = rand()%(n - m + 1) + m; 5 5 int r = rand()%(8-0)+0; 6 6 num = r; 7 7 cout << "constuctor C ,num = " << num << endl; 8 8 if(!(r % 4)){ 9 9 throw r; 10 10 } 11 11 } 12 12 ~C(){ 13 13 cout << "destructor C, num = " << num << endl; 14 14 } 15 15 private: 16 16 int num; 17 17 }; 18 18 19 19 int main(void){ 20 20 21 21 22 22 C arr[10]; 23 23 24 24 }
當隨機數r 整除 4是,即throw異常,則前面成功構造的對象不會析構
若注釋第9行的throw r 結果是:
第3、4兩種場景,在第二篇文章驗證:《C++構造函數異常(二)》