異常處理基本思想
C++的異常處理的基本思想大致可以概括為傳統錯誤處理機制、通過函數傳回值來處理錯誤。
1)C++的異常處理機制使得異常的引發和異常的處理不必在同一個函數中,這樣底層的函數可以著重解決具體問題,而不必過多的考慮異常的處理。上層調用者可以再適當的位置設計對不同類型異常的處理。
2)異常是專門針對抽象編程中的一系列錯誤處理的,C++中不能藉助函數機制,因為棧結構的本質是先進後出,依次訪問,無法進行跳躍,但錯誤處理的特徵卻是遇到錯誤資訊就想要轉到若干級之上進行重新嘗試,如圖
3)異常超脫於函數機制,決定了其對函數的跨越式回跳。
4)異常跨越函數
異常基本文法
1) 若有異常則通過throw操作建立一個異常對象並拋擲。
2) 將可能拋出異常的程式段嵌在try塊之中。控制通過正常的順序執行到達try語句,然後執行try塊內的保護段。
3) 如果在保護段執行期間沒有引起異常,那麼跟在try塊後的catch子句就不執行。程式從try塊後跟隨的最後一個catch子句後面的語句繼續執行下去。
4) catch子句按其在try塊後出現的順序被檢查。匹配的catch子句將捕獲並處理異常(或繼續拋擲異常)。
5) 如果匹配的處理器未找到,則運行函數terminate將被自動調用,其預設功能是調用abort終止程式。
6)處理不了的異常,可以在catch的最後一個分支,使用throw文法,向上扔
7)異常機制與函數機制互不干涉,但捕捉的方式是基於類型匹配。捕捉相當於函數傳回型別的匹配,而不是函數參數的匹配,所以捕捉不用考慮一個拋擲中的多種資料類型匹配問題。
catch代碼塊必須出現在try後,並且在try塊後可以出現多個catch代碼塊,以捕捉各種不同類型的拋擲。
異常機制是基於這樣的原理:程式運行實質上是資料實體在做一些操作,因此發生異常現象的地方,一定是某個實體出了差錯,該實體所對應的資料類型便作為拋擲和捕捉的依據。
8)異常捕捉嚴格按照類型匹配
異常捕捉的類型匹配之苛刻程度可以和模板的類型匹配媲美,它不允許相容類型的隱式轉換,比如,拋擲char類型用int型就捕捉不到.例如下列代碼不會輸出“int exception.”,從而也不會輸出“That's ok.” 因為出現異常後提示退出
int main(){ try{ throw ‘H'; } catch (int){ cout << "int exception.\n"; } cout << "That's ok.\n"; return 0; }
棧解旋(unwinding)
異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象,都會被自動析構。析構的順序與構造的順序相反。這一過程稱為棧的解旋(unwinding)。
#include <iostream> #include <cstdio> using namespace std; class MyException {}; class Test { public: Test(int a = 0, int b = 0) { this->a = a; this->b = b; cout << "Test 建構函式執行" << "a:" << a << " b: " << b << endl; } void printT() { cout << "a:" << a << " b: " << b << endl; } ~Test() { cout << "Test 解構函式執行" << "a:" << a << " b: " << b << endl; } private: int a; int b; }; void myFunc() throw (MyException) { Test t1; Test t2; cout << "定義了兩個棧變數,異常拋出後測試棧變數的如何被析構" << endl; throw MyException(); } int main() { //異常被拋出後,從進入try塊起,到異常被拋擲前,這期間在棧上的構造的所有對象, //都會被自動析構。析構的順序與構造的順序相反。 //這一過程稱為棧的解旋(unwinding) try { myFunc(); } //catch(MyException &e) //這裡不能訪問異常對象 catch (MyException) //這裡不能訪問異常對象 { cout << "接收到MyException類型異常" << endl; } catch (...) { cout << "未知類型異常" << endl; } return 0; }
異常介面聲明
1)為了加強程式的可讀性,可以在函式宣告中列出可能拋出的所有異常類型,例如:
void func() throw (A, B, C , D); //這個函數func()能夠且只能拋出類型A B C D及其子類型的異常。
2)如果在函式宣告中沒有包含異常介面聲明,則次函數可以拋擲任何類型的異常,例如:
void func();
3)一個不拋擲任何類型異常的函數可以聲明為:
void func() throw();
4) 如果一個函數拋出了它的異常介面聲明所不允許拋出的異常,unexpected函數會被調用,該函數預設行為調用terminate函數中止程式。
傳統處理錯誤
#include <iostream> #include <cstdio> using namespace std; // 傳統的錯誤處理機制 int myStrcpy(char *to, char *from) { if (from == NULL) { return 1; } if (to == NULL) { return 2; } // copy時的情境檢查 if (*from == 'a') { return 3; // copy時錯誤 } while (*from != '\0') { *to = *from; to++; from++; } *to = '\0'; return 0; } int main() { int ret = 0; char buf1[] = "zbcdefg"; char buf2[1024] = { 0 }; ret = myStrcpy(buf2, buf1); if (ret != 0) { switch (ret) { case 1: cout << "源buf出錯!\n"; break; case 2: cout << "目的buf出錯!\n"; break; case 3: cout << "copy過程出錯!\n"; break; default: cout << "未知錯誤!\n"; break; } } cout << "buf2:\n" << buf2; cout << endl; return 0; }
throw char*
#include <iostream> #include <cstdio> using namespace std; // throw char * void myStrcpy(char *to, char *from) { if (from == NULL) { throw "源buf出錯"; } if (to == NULL) { throw "目的buf出錯"; } // copy時的情境檢查 if (*from == 'a') { throw "copy過程出錯"; // copy時錯誤 } while (*from != '\0') { *to = *from; to++; from++; } *to = '\0'; return; } int main() { int ret = 0; char buf1[] = "abcdefg"; char buf2[1024] = { 0 }; try { myStrcpy(buf2, buf1); } catch (int e) // e可以寫可以不寫 { cout << e << "int類型異常" << endl; } catch (char *e) { cout << "char* 類型異常" << endl; } catch (...) { }; cout << endl; return 0; }
throw 類對象
#include <iostream> #include <cstdio> using namespace std; class BadSrcType {}; class BadDestType {}; class BadProcessType { public: BadProcessType() { cout << "BadProcessType建構函式do \n"; } BadProcessType(const BadProcessType &obj) { cout << "BadProcessType copy建構函式do \n"; } ~BadProcessType() { cout << "BadProcessType解構函式do \n"; } };
throw 類對象、類型異常
void my_strcpy3(char *to, char *from) { if (from == NULL) { throw BadSrcType(); } if (to == NULL) { throw BadDestType(); } //copy是的 情境檢查 if (*from == 'a') { printf("開始 BadProcessType類型異常 \n"); throw BadProcessType(); //會不會產生一個匿名對象? } if (*from == 'b') { throw &(BadProcessType()); //會不會產生一個匿名對象? } if (*from == 'c') { throw new BadProcessType; //會不會產生一個匿名對象? } while (*from != '\0') { *to = *from; to++; from++; } *to = '\0'; } int main() { int ret = 0; char buf1[] = "cbbcdefg"; char buf2[1024] = { 0 }; try { //my_strcpy1(buf2, buf1); //my_strcpy2(buf2, buf1); my_strcpy3(buf2, buf1); } catch (int e) //e可以寫 也可以不寫 { cout << e << " int類型異常" << endl; } catch (char *e) { cout << e << " char* 類型異常" << endl; } //--- catch (BadSrcType e) { cout << " BadSrcType 類型異常" << endl; } catch (BadDestType e) { cout << " BadDestType 類型異常" << endl; } //結論1: 如果 接受異常的時候 使用一個異常變數,則copy構造異常變數. /* catch( BadProcessType e) //是把匿名對象copy給e 還是e還是那個匿名對象 { cout << " BadProcessType 類型異常" << endl; } */ /*結論2: 使用引用的話 會使用throw時候的那個對象 catch( BadProcessType &e) //是把匿名對象copy給e 還是e還是那個匿名對象 { cout << " BadProcessType 類型異常" << endl; } */ //結論3: 指標可以和引用/元素寫在一塊 但是引用和元素不能寫在一塊 catch (BadProcessType *e) //是把匿名對象copy給e 還是e還是那個匿名對象 { cout << " BadProcessType 類型異常" << endl; delete e; } //結論4: 類對象時, 使用引用比較合適 // -- catch (...) { cout << "未知 類型異常" << endl; } return 0; }