構造函數中可不能夠拋出異常?固然能夠。從語法上來講,是能夠的;從實際狀況來看,如今的軟件系統日漸龐大和複雜,很難保證 Constructor 在執行過程當中徹底不發生一點異常。html
那麼,若是構造函數中拋出異常,會發生什麼狀況呢?ios
1、構造函數中拋出異常將致使對象的析構函數不被執行。
C++僅能 delete 被徹底構造的對象(fully constructed objects),只有一個對象的構造函數徹底運行完畢,這個對象才被徹底地構造。因此若是在構造函數中拋出一個異常,這個異常將傳遞到建立對象的地方(程序控制權也會隨之轉移),這樣對象就只是部分被構造,它的析構函數將不會被執行。安全
看下面的示例:函數
#pragma once #include <iostream> #include <string> using namespace std; /******************類定義**********************/ class person { public: person(const string& str):name(str) { //throw exception("測試:在構造函數中拋出一個異常"); cout << "構造一個對象!" << endl; }; ~person() { cout << "銷燬一個對象!" << endl; }; private: string name; }; /******************測試類**********************/ int main() { try { person me("songlee"); } catch(exception e) { cout << e.what() << endl; }; getchar(); return 0; }
注意上面的 me 是一個局部對象,因此離開try{}
的做用域,會自動執行析構函數。運行上述代碼,輸出結果以下:測試
構造一個對象! 銷燬一個對象!
若是在構造函數中拋出一個異常(去掉註釋),輸出結果以下:url
測試:在構造函數中拋出一個異常
能夠看出,析構函數沒有被自動執行。爲何「構造一個對象!」也沒有輸出呢?由於程序控制權轉移了,因此在異常點之後的語句都不會被執行。spa
2、構造函數拋出異常可能致使內存泄露
#pragma once #include <string> #include <iostream> using namespace std; class A { public: A(){}; }; class B { public: B() { //throw exception("測試:在B的構造函數中拋出一個異常"); cout << "構造 B 對象!" << endl; }; ~B(){ cout << "銷燬 B 對象!" << endl; }; }; class Tester { public: Tester(const string& name, const string& address); ~Tester(); private: string theName; string theAddress; A *a; B *b; };
上面聲明瞭三個類(A、B、Tester),其中Tester類的構造函數和析構函數定義以下:.net
Tester::Tester(const string& name, const string& address): theName(name), theAddress(address) { a = new A(); b = new B(); // <—— cout << "構造 Tester 對象!" << endl; } Tester::~Tester() { delete a; delete b; cout << "銷燬 Tester 對象!" << endl; }
在構造函數中,動態的分配了內存空間給a、b兩個指針。析構函數負責刪除這些指針,確保Tester對象不會發生內存泄露(C++中delete一個空指針也是安全的)。設計
int main() { Tester *tes = NULL; try { tes = new Tester("songlee","201"); } catch(exception e) { cout << e.what() << endl; }; delete tes; // 刪除NULL指針是安全的 getchar(); return 0; }
運行輸出結果:指針
構造 B 對象! 構造 Tester 對象! 銷燬 B 對象! 銷燬 Tester 對象!
看上去好像一切良好,在正常狀況下確實沒有錯。可是在有異常的狀況下,恐怕就不會良好了。
試想在 Tester 的構造函數執行時,b = new B()
拋出了異常:多是由於operator new不能給B對象分配足夠的內存,也多是由於 B 的構造函數本身拋出了一個異常。不論什麼緣由,在 Tester 構造函數內拋出異常,這個異常將傳遞到創建 Tester 對象的地方(程序控制權也會轉移)。
在 B 的構造函數裏拋出異常(去掉註釋)時,程序運行結果以下:
測試:在B的構造函數中拋出一個異常
能夠看出,C++拒絕爲沒有完成構造操做的對象調用析構函數,即便你使用了delete
語句。因爲 Tester 的析構函數不會執行,因此給A對象 a 動態分配(new)的空間沒法釋放,將形成內存泄露。
注:不用爲 Tester 對象中的非指針數據成員操心,由於它們不是new出來的,且在異常拋出以前已經構造徹底,因此它們會自動逆序析構。
3、解決上述內存泄露的方法
由於當對象在構造中拋出異常後C++不負責清除(動態分配)的對象,因此你必須從新設計構造函數以讓它們本身清除。經常使用的方法是捕獲全部的異常,而後執行一些清除代碼,最後再從新拋出異常讓它繼續傳遞。
示例代碼以下:
Tester::Tester(const string& name, const string& address): theName(name), theAddress(address), a(NULL), // 初始化爲空指針是必須的 b(NULL) { try { a = new A(); b = new B(); } catch(...) // 捕獲全部異常 { delete a; delete b; throw; // 繼續傳遞異常 } }
另外一種更好的方法是使用智能指針(smart pointer),不過關於智能指針的內容比較多,在這裏就不說了。
總結:
-
在構造函數中拋出異常是C++中通知對象構造失敗的惟一方法。
-
構造函數中拋出異常,對象的析構函數將不會被執行。
-
構造函數拋出異常時,本應該在析構函數中被delete的對象沒有被delete,會致使內存泄露。
-
當對象發生部分構造時,已經構造完畢的子對象(非動態分配)將會逆序地被析構。