參考: https://blog.csdn.net/xueluowutong/article/details/81257654ios
在c++中,能夠直接拋出異常以後本身進行捕捉處理,如:(這樣就能夠在任何本身獲得不想要的結果的時候進行中斷,好比在進行數據庫事務操做的時候,若是某一個語句返回SQL_ERROR則直接拋出異常,在catch塊中進行事務回滾(回滾怎麼理解?))。c++
也能夠本身定義異常類來進行處理:程序員
同時也可使用標準異常類進行處理:數據庫
1、簡單的例子安全
首先經過一個簡單的例子來熟悉C++ 的 try/catch/throw(可根據單步調試來熟悉,try catch throw部分是如何運行的):app
catch 的數據類型須要與throw出來的數據類型相匹配的。dom
2、catch(...)的做用函數
catch(…)可以捕獲多種數據類型的異常對象,因此它提供給程序員一種對異常對象更好的控制手段,使開發的軟件系統有很好的可靠性。所以一個比較有經驗的程序員一般會這樣組織編寫它的代碼模塊,以下:性能
3、異常中採用面向對象的處理優化
首先看下面的例子:
下面是更多面向對象和異常處理結合的例子:
這是輸出信息: Construct Test Construct my throw Destruct my throw **************** Destruct my throw (這裏是異常處理空間中對異常類的拷貝的析構) Destruct Test ======================================
不過通常來講咱們可能更習慣於把會產生異常的語句和要throw的異常類分紅不一樣的類來寫,下面的代碼能夠是咱們更願意書寫的:
class ExceptionClass { public: ExceptionClass(const char* name="Exception Default Class") { cout<<"Exception Class Construct String"<<endl; } ~ExceptionClass() { cout<<"Exception Class Destruct String"<<endl; } void ReportError() { cout<<"Exception Class:: This is Report Error Message"<<endl; } }; class ArguClass { char* name; public: ArguClass(char* name="default name") { cout<<"Construct String::"<<name<<endl; this->name=name; } ~ArguClass() { cout<<"Destruct String::"<<name<<endl; } void mythrow() { throw ExceptionClass("my throw"); } }; _tmain() { ArguClass e("haha"); try { e.mythrow(); } catch(int) { cout<<"If This is Message display screen, This is a Error!!"<<endl; //這行不會執行 } catch(ExceptionClass pTest) { pTest.ReportError(); } catch(...) { cout<<"***************"<<endl; } }
輸出Message: Construct String::haha Exception Class Construct String Exception Class Destruct String Exception Class:: This is Report Error Message Exception Class Destruct String Destruct String::haha
4、構造和析構中的異常拋出 先看個程序,假如我在構造函數的地方拋出異常,這個類的析構會被調用嗎?可若是不調用,那類裏的東西豈不是不能被釋放了?
#include <iostream.h> #include <stdlib.h> class ExceptionClass1 { char* s; public: ExceptionClass1() { cout<<"ExceptionClass1()"<<endl; s=new char[4]; cout<<"throw a exception"<<endl; throw 18; } ~ExceptionClass1() { cout<<"~ExceptionClass1()"<<endl; delete[] s; } }; void main() { try { ExceptionClass1 e; } catch(...) {} }
結果爲: ExceptionClass1() throw a exception
在這兩句輸出之間,咱們已經給S分配了內存,但內存沒有被釋放(由於它是在析構函數中釋放的)。應該說這符合實際現象,由於對象沒有完整構造。
爲了不這種狀況,我想你也許會說:應避免對象經過自己的構造函數涉及到異常拋出。即:既不在構造函數中出現異常拋出,也不該在構造函數調用的一切東西中出現異常拋出。 可是在C++中能夠在構造函數中拋出異常,經典的解決方案是使用STL的標準類auto_ptr。
那麼,在析構函數中的狀況呢?咱們已經知道,異常拋出以後,就要調用自己的析構函數,若是這析構函數中還有異常拋出的話,則已存在的異常還沒有被捕獲,會致使異常捕捉不到。
5、標準C++異常類
標準異常都派生自一個公共的基類exception。基類包含必要的多態性函數提供異常描述,能夠被重載。下面是exception類的原型:
class exception { public: exception() throw(); exception(const exception& rhs) throw(); exception& operator=(const exception& rhs) throw(); virtual ~exception() throw(); virtual const char *what() const throw(); }; C++有不少的標準異常類: namespace std { //exception派生 class logic_error; //邏輯錯誤,在程序運行前能夠檢測出來 //logic_error派生 class domain_error; //違反了前置條件 class invalid_argument; //指出函數的一個無效參數 class length_error; //指出有一個超過類型size_t的最大可表現值長度的對象的企圖 class out_of_range; //參數越界 class bad_cast; //在運行時類型識別中有一個無效的dynamic_cast表達式 class bad_typeid; //報告在表達試typeid(*p)中有一個空指針p //exception派生 class runtime_error; //運行時錯誤,僅在程序運行中檢測到 //runtime_error派生 class range_error; //違反後置條件 class overflow_error; //報告一個算術溢出 class bad_alloc; //存儲分配錯誤 }
標準庫異常類定義在如下四個頭文件中
一、exception頭文件:定義了最多見的標準異常類,其類名爲exception。只通知異常的產生,但不會提供更多的信息
二、stdexcept頭文件定義瞭如下幾種常見異常類
函數 功能或做用
exception 最多見的問題
runtime_error 運行時錯誤:僅在運行時才能檢測到的問題
range_error 運行時錯誤:生成的結果超出了有意義的值域範圍
overflow_error 運行時錯誤:計算上溢
underflow_error 運行時錯誤:計算下溢
logic_error 邏輯錯誤:可在運行前檢測到的問題
domain_error 邏輯錯誤:參數的結果值不存在
invalid_argument 邏輯錯誤:不合適的參數
length_error 邏輯錯誤:試圖生成一個超出該類型最大長度的對象
out_of_range 邏輯錯誤:使用一個超出有效範圍的值
三、new頭文件定義了bad_alloc異常類型,提供因沒法分配內存而由new拋出的異常
四、type_info頭文件定義了bad_cast異常類型(要使用type_info必須包含typeinfo頭文件)
下面是使用異常類的例子:
首先,我定義了幾個異常類,這些類也能夠從標準異常類進行派生,以下
class BadInitializers { public: BadInitializers() {} }; class OutOfBounds { public: OutOfBounds(int i) { cout<<"Size "<<i<<" is illegal!!!"<<endl; } }; class SizeMismatch { public: SizeMismatch() {} };
而後要在程序中須要的地方使用throw來拋出異常類,兩個拋出異常類的例子以下
template <class T> Array1D<T>::Array1D(int sz) { if(sz<0) { //throw BadInitializers(); throw invalid_argument("Size has to be bigger than 0!!!"); } size=sz; element=new T[size]; } template <class T> T &Array1D<T>::operator[](int i) const { if(i<0||i>=size) { throw OutOfBounds(i); } return element[i]; }
而後在主程序中使用try...catch...來捕獲異常,並進行相應的處理,以下
try { int i=0; Array1D<int> a1(5); a1[0]=1; a1[1]=3; a1[2]=5; a1[3]=7; a1[4]=8; Array1D<int> a2(a1); for(i=0;i<a2.Size();i++) { cout<<a2[i]<<" "; } cout<<endl; Array1D<int> a3(5); a3=a1+a2; cout<<a3; } catch(BadInitializers) { cout<<"Error:BadInitializers!!!"<<endl; } catch(OutOfBounds &e) { cout<<"Error:OutOfBounds!!!"<<endl; } catch(SizeMismatch &e) { cout<<"Error:SizeMismatch!!!"<<endl; } catch(invalid_argument &e) { cout<<"Error:"<<e.what()<<endl; } catch(...) { cout<<"An unknown error!!!"<<endl; }
6、try finally使用
__try { file://保護塊 } __finally { file://結束處理程序 } 在上面的代碼段中,操做系統和編譯程序共同來確保結束處理程序中的__f i n a l l y代碼塊可以被執行,無論保護體(t r y塊)是如何退出的。不論你在保護體中使用r e t u r n,仍是g o t o,或者是longjump,結束處理程序(f i n a l l y塊)都將被調用。
咱們來看一個實列:(返回值:10, 沒有Leak,性能消耗:小)
DWORD Func_SEHTerminateHandle() { DWORD dwReturnData = 0; HANDLE hSem = NULL; const char* lpSemName = "TermSem"; hSem = CreateSemaphore(NULL, 1, 1, lpSemName); __try { WaitForSingleObject(hSem,INFINITE); dwReturnData = 5; } __finally { ReleaseSemaphore(hSem,1,NULL); CloseHandle(hSem); } dwReturnData += 5; return dwReturnData; }
這段代碼應該只是作爲一個基礎函數,咱們將在後面修改它,來看看結束處理程序的做用: ==================== 在代碼加一句:(返回值:5, 沒有Leak,性能消耗:中下)
DWORD Func_SEHTerminateHandle() { DWORD dwReturnData = 0; HANDLE hSem = NULL; const char* lpSemName = "TermSem"; hSem = CreateSemaphore(NULL, 1, 1, lpSemName); __try { WaitForSingleObject(hSem,INFINITE); dwReturnData = 5; return dwReturnData; } __finally { ReleaseSemaphore(hSem,1,NULL); CloseHandle(hSem); } dwReturnData += 5; return dwReturnData; }
在try塊的末尾增長了一個return語句。這個return語句告訴編譯程序在這裏要退出這個函數並返回dwTemp變量的內容,如今這個變量的值是5。可是,若是這個return語句被執行,該線程將不會釋放信標,其餘線程也就不能再得到對信標的控制。能夠想象,這樣的執行次序會產生很大的問題,那些等待信標的線程可能永遠不會恢復執行。 經過使用結束處理程序,能夠避免return語句的過早執行。當return語句試圖退出try塊時,編譯程序要確保finally塊中的代碼首先被執行。要保證finally塊中的代碼在try塊中的return語句退出以前執行。在程序中,將ReleaseSemaphore的調用放在結束處理程序塊中,保證信標總會被釋放。這樣就不會形成一個線程一直佔有信標,不然將意味着全部其餘等待信標的線程永遠不會被分配CPU時間。 在finally塊中的代碼執行以後,函數實際上就返回。任何出如今finally塊之下的代碼將再也不執行,由於函數已在try塊中返回。因此這個函數的返回值是5,而不是10。 讀者可能要問編譯程序是如何保證在try塊能夠退出以前執行finally塊的。當編譯程序檢查源代碼時,它看到在try塊中有return語句。這樣,編譯程序就生成代碼將返回值(本例中是5)保存在一個編譯程序創建的臨時變量中。編譯程序而後再生成代碼來執行f i n a l l y塊中包含的指令,這稱爲局部展開。更特殊的狀況是,因爲try塊中存在過早退出的代碼,從而產生局部展開,致使系統執行finally塊中的內容。在finally塊中的指令執行以後,編譯程序臨時變量的值被取出並從函數中返回。 能夠看到,要完成這些事情,編譯程序必須生成附加的代碼,系統要執行額外的工做。
finally塊的總結性說明 咱們已經明確區分了強制執行finally塊的兩種狀況: • 從try塊進入finally塊的正常控制流。 • 局部展開:從try塊的過早退出(goto、long jump、continue、break、return等)強制控制轉移到finally塊。 第三種狀況,全局展開( global unwind),這個之後再看。
7、C++異常參數傳遞
從語法上看,在函數裏聲明參數與在catch子句中聲明參數是同樣的,catch裏的參數能夠是值類型,引用類型,指針類型。例如:
try { ..... } catch(A a) { } catch(B& b) { } catch(C* c) { }
儘管表面是它們是同樣的,可是編譯器對兩者的處理卻又很大的不一樣。調用函數時,程序的控制權最終還會返回到函數的調用處,可是拋出一個異常時,控制權永遠不會回到拋出異常的地方。
class A; void func_throw() { A a; throw a; //拋出的是a的拷貝,拷貝到一個臨時對象裏 } try { func_throw(); } catch(A a) //臨時對象的拷貝 { }
當咱們拋出一個異常對象時,拋出的是這個異常對象的拷貝。當異常對象被拷貝時,拷貝操做是由對象的拷貝構造函數完成的。該拷貝構造函數是對象的靜態類型(static type)所對應類的拷貝構造函數,而不是對象的動態類型(dynamic type)對應類的拷貝構造函數。此時對象會丟失RTTI信息。 異常是其它對象的拷貝,這個事實影響到你如何在catch塊中再拋出一個異常。好比下面這兩個catch塊,乍一看好像同樣:
catch (A& w) // 捕獲異常 { // 處理異常 throw; // 從新拋出異常,讓它繼續傳遞 } catch (A& w) // 捕獲Widget異常 { // 處理異常 throw w; // 傳遞被捕獲異常的拷貝 }
第一個塊中從新拋出的是當前異常(current exception),不管它是什麼類型。(有多是A的派生類) 第二個catch塊從新拋出的是新異常,失去了原來的類型信息。 通常來講,你應該用throw來從新拋出當前的異常,由於這樣不會改變被傳遞出去的異常類型,並且更有效率,由於不用生成一個新拷貝。 看看如下這三種聲明: catch (A w) ... // 經過傳值 catch (A& w) ... // 經過傳遞引用,一個被異常拋出的對象(老是一個臨時對象)能夠經過普通的引用捕獲 catch (const A& w) ... //const引用
回到異常對象拷貝上來。咱們知道,當用傳值的方式傳遞函數的參數,咱們製造了被傳遞對象的一個拷貝,並把這個拷貝存儲到函數的參數裏。一樣咱們經過傳值的方式傳遞一個異常時,也是這麼作的當咱們這樣聲明一個catch子句時: catch (A w) ... // 經過傳值捕獲 會創建兩個被拋出對象的拷貝,一個是全部異常都必須創建的臨時對象,第二個是把臨時對象拷貝進w中。實際上,編譯器會優化掉一個拷貝。一樣,當咱們經過引用捕獲異常時, catch (A& w) ... // 經過引用捕獲 catch (const A& w) ... //const引用捕獲 這仍舊會創建一個被拋出對象的拷貝:拷貝是一個臨時對象。相反當咱們經過引用傳遞函數參數時,沒有進行對象拷貝。話雖如此,可是不是全部編譯器都如此。
另外,經過指針拋出異常與經過指針傳遞參數是相同的。不論哪一種方法都是一個指針的拷貝被傳遞。你不能認爲拋出的指針是一個指向局部對象的指針,由於當異常離開局部變量的生存空間時,該局部變量已經被釋放。Catch子句將得到一個指向已經不存在的對象的指針。這種行爲在設計時應該予以免。 另一個重要的差別是在函數調用者或拋出異常者與被調用者或異常捕獲者之間的類型匹配的過程不一樣。在函數傳遞參數時,若是參數不匹配,那麼編譯器會嘗試一個類型轉換,若是存在的話。而對於異常處理的話,則徹底不是這樣。見一下的例子:
void func_throw() { CString a; throw a; //拋出的是a的拷貝,拷貝到一個臨時對象裏 } try { func_throw(); } catch(const char* s) { }
拋出的是CString,若是用const char*來捕獲的話,是捕獲不到這個異常的。 儘管如此,在catch子句中進行異常匹配時能夠進行兩種類型轉換。第一種是基類與派生類的轉換,一個用來捕獲基類的catch子句也能夠處理派生類類型的異常。反過來,用來捕獲派生類的沒法捕獲基類的異常。 第二種是容許從一個類型化指針(typed pointer)轉變成無類型指針(untyped pointer),因此帶有const void* 指針的catch子句能捕獲任何類型的指針類型異常: catch (const void*) ... //能夠捕獲全部指針異常 另外,你還能夠用catch(...)來捕獲全部異常,注意是三個點。 傳遞參數和傳遞異常間最後一點差異是catch子句匹配順序老是取決於它們在程序中出現的順序。所以一個派生類異常可能被處理其基類異常的catch子句捕獲,這叫異常截獲,通常的編譯器會有警告。
class A { public: A() { cout << "class A creates" << endl; } void print() { cout << "A" << endl; } ~A() { cout << "class A destruct" << endl; } }; class B: public A { public: B() { cout << "class B create" << endl; } void print() { cout << "B" << endl; } ~B() { cout << "class B destruct" << endl; } }; void func() { B b; throw b; } try { func(); } catch( B& b) //必須將B放前面,若是把A放前面,B放後面,那麼B類型的異常會先被截獲。 { b.print(); } catch (A& a) { a.print() ; }