異常是指程序在運行過程當中產生可預料的執行分支。如除0操做,數組訪問越界、要打開的文件不存在。
Bug是指程序中的錯誤,是不被預期的運行方式。如野指針、堆空間使用結束未釋放。
C語言中處理異常的方式通常是使用if....else...分支語句。ios
double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { cout << "a is devieded by zero" <<endl; } return ret; }
C語言經過setjmp和longjmp對異常處理進行優化。int setjmp(jmp_buf env);
將上下文保存到jmp_buf結構體中void longjmp(jmp_buf env, int value);
從jmp_buf結構體中恢復setjmp保存的上下文,最終從setjmp函數調用點返回,返回值爲value。數組
#include <iostream> #include <csetjmp> using namespace std; static jmp_buf env; double divide(double a, double b) { const double delta = 0.000000000000001; double ret = 0; if( !((-delta < b) && (b < delta)) ) { ret = a / b; } else { longjmp(env, 1); } return ret; } int main(int argc, char *argv[]) { if( setjmp(env) == 0 ) { double r = divide(1, 1); cout << "r = " << r << endl; } else { cout << "Divided by zero..." << endl; } return 0; }
C++語言中內置了異常處理的語法,try.....catch......。
try語句塊用來處理正常代碼邏輯,catch語句塊用來處理異常處理狀況,throw拋出異常。在try語句塊拋出的異常在相應的catch語句塊捕獲處理。
同一個try語句塊能夠對應多個catch語句塊,catch語句塊能夠定義具體處理的異常類型,不一樣的類型的異常由不一樣的catch語句塊處理,try語句塊能夠拋出任何類型的異常,catch(...)用於處理全部類型的異常,任何異常都只能被捕獲一次。
throw拋出的異常必須被catch處理,若是當前函數可以處理異常,繼續執行;若是當前函數不能處理異常,函數中止執行並返回。未被處理的異常會順着函數調用棧向上傳遞,直到被處理爲止,不然程序將中止執行。
異常處理的匹配:
A、異常拋出後從上到下嚴格匹配每一個catch語句塊處理的類型,不能進行任何類型轉換。
B、catch(...)語句塊只能放到catch語句塊分支的最後位置。
異常處理的使用實例:ide
try { throw 'c'; } catch(char c) { cout << "catch(char c)" << endl; } catch(short c) { cout << "catch(short c)" << endl; } catch(double c) { cout << "catch(double c)" << endl; } catch(...) { cout << "catch(...)" << endl; }
catch語句塊捕獲的異常從新解釋後能夠拋出異常,拋出的異常在外層的try...catch中捕獲。函數
try { try { throw 'c'; } catch(int i) { cout << "Inner: catch(int i)" << endl; throw i; } catch(...) { cout << "Inner: catch(...)" << endl; throw; } } catch(...) { cout << "Outer: catch(...)" << endl; }
一般在catch語句塊中捕獲的異常從新解釋後能夠再次拋出異常,工程實踐中一般用於統一異常類型,如經過捕獲第三方庫函數中拋出的異常,從新解釋後拋出統一的異常處理信息。
異常的類型能夠是自定義類型,自定義類型的異常匹配依舊是自上而下嚴格匹配,但因爲賦值兼容性原則在異常匹配中適用,因此匹配子類異常的catch語句塊放在catch分支的上部,匹配父類異常的catch語句塊放在catch分支的下部。學習
#include <iostream> using namespace std; class Parent { public: Parent(int i):code(i) { } private: int code; }; class Child : public Parent { public: Child(int i):Parent(i),code(i) { } private: int code; }; int main(int argc, char *argv[]) { try { Child child(1); throw child; } catch(const Child& e) { cout << "catch(const Child& e)" << endl; } catch(const Parent& e) { cout << "catch(const Parent& e)" << endl; } return 0; }
STL提供了實用的異常處理類,STL中的異常都是從exception類繼承而來,exception類只要有兩個分支,logic_error和runtime_error。logic_error用於處理程序中可避免邏輯錯誤,runtime_error用於處理程序中沒法處理的惡性錯誤。
優化
#include <iostream> #include <stdexcept> using namespace std; int main(int argc, char *argv[]) { int array[5] = {0}; for(int i = 0; i < 5; i++) { array[i] = i; } try { for(int i = 0; i < 10; i++) { if(i >= 5) { throw out_of_range("out of range"); } else { cout << array[i] <<endl; } } } catch(const out_of_range& e) { cout << e.what() << endl; } return 0; }
try...catch語句用於分隔正常功能代碼與異常處理代碼。try...catch語句也能夠將函數體分隔爲兩部分。
函數聲明和定義時能夠直接指定可能拋出的異常類型,異常聲明做爲函數的一部分能夠提升代碼可讀性。
函數異常聲明是一種與編譯器之間的契約,函數聲明異常後就只能拋出聲明的異常。若是拋出其它異常將會致使程序運行終止。也能夠經過函數異常聲明定義無異常函數。spa
#include <iostream> using namespace std; //聲明拋出的異常類型爲int void func(int i, int j)throw(int) { if(0 < j && j < 10) { } else { throw 0; } } void test(int i)try { func(i,i); } catch(int i) { cout << "catch(int i): " << i << endl; } catch(...) { cout << "Exception:" << endl; } int main(int argc, char *argv[]) { test(10); test(1); return 0; }
上述代碼中,func函數聲明瞭拋出的異常類型爲int,所以func函數只能拋出int類型異常,若是拋出其它類型異常將致使程序運行終止。即便test函數能夠對拋出的其它類型異常進行捕獲,程序也會運行終止。
若是函數內部可能會拋出多種類型的異常,須要在函數聲明異常時指定聲明的異常類型,代碼以下:指針
#include <iostream> #include <string> using namespace std; //聲明拋出的異常類型爲int,char,string void func(int i, int j)throw(int,char,string) { if(0 < j && j < 10) { throw j; } if(10 < j && j < 100) { throw 'A'; } else { throw string("string exception."); } } void test(int i)try { func(i,i); } catch(int i) { cout << "catch(int i): " << i << endl; } catch(char c) { cout << "Exception:" << c << endl; } catch(string s) { cout << s << endl; } catch(...) { cout << "Exception:" << endl; } int main(int argc, char *argv[]) { test(115);//string exception. test(1);//catch(int i): 1 test(20);//Exception:A return 0; }
上述代碼中,func函數能夠拋出多種類型的異常,test函數會捕獲func函數拋出的多種異常類型。rest
若是異常沒有被處理,terminate函數會被自動調用。terminate函數是整個程序釋放系統資源的最後機會。默認狀況下,terminate函數調用abort庫函數終止程序。abort函數使得程序執行異常而當即退出。code
#include <iostream> using namespace std; class Test { public: Test() { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } }; int main(int argc, char *argv[]) { static Test test; throw 1; return 0; }
上述代碼運行結果以下:
C++支持使用自定義的terminate函數實現替換默認的terminate函數實現。
自定義terminate函數的實現規則以下:
A、自定義一個無返回值、無參數的函數
B、不能拋出任何異常
C、必須以某種方式結束當前程序
經過調用set_terminate函數能夠設置自定義的terminate結束函數,其用法以下:
A、參數類型爲void (*)()
B、返回值爲默認的terminate函數入口地址
#include <iostream> #include <cstdlib> using namespace std; class Test { public: Test() { cout << "Test()" << endl; } ~Test() { cout << "~Test()" << endl; } }; void terminate_test() { cout << "void terminate_test()" << endl; exit(1); } int main(int argc, char *argv[]) { set_terminate(terminate_test); static Test test; throw 1; return 0; } // output: // Test() // void terminate_test() // ~Test()
上述代碼在最終terminate_test結束函數中調用了exit(1),exit函數會確保程序中全局、靜態數據區的對象被正確銷燬。若是使用abort函數替換exit函數,程序運行結果以下:
析構函數中拋出異常可能會致使最終結束函數terminate函數會被重複調用。
C++語言提供用於聲明函數拋出異常的語法聲明。異常聲明做爲函數聲明的修飾符,位於函數參數表的後面。函數異常聲明的示例以下:
//可能拋出任何異常 void func1(); //只能拋出的異常類型:char,int void func2() throw(char, int); //不拋出任何異常 void func3() throw();
函數異常聲明的意義以下:
A、提示函數調用者必須作好異常處理的準備
B、提示函數的維護者不要拋出其它異常
C、函數異常規格說明是函數接口的一部分
若是函數拋出的異常類型不在函數異常聲明中,全局unexpected()函數會被調用。默認的unexpected()函數會調用全局的terminate函數,能夠自定義函數替換默認的unexpected()函數實現。
自定義的unexpected()函數的實現規則以下:
A、自定義一個無返回值、無參數的函數
B、可以再次拋出異常,當異常符合觸發函數的異常規格說明時,恢復程序執行。不然,調用全局terminate函數結束程序。
經過調用set_unexpected函數能夠設置自定義的unexpected()函數,用法以下:
A、參數類型爲void (*)()
B、返回值爲默認的unexpected()函數入口地址。
#include <iostream> #include <cstdlib> using namespace std; void func() throw(int) { cout << "void func()throw(int)" << endl; throw 'A'; } void unexpected_test() { cout << "void unexpected_test()" << endl; throw 1; } int main(int argc, char *argv[]) { set_unexpected(unexpected_test); try { func(); } catch(int) { cout << "catch(int)" << endl; } catch(char) { cout << "catch(char)" << endl; } return 0; } // output: // void func()throw(int) // void unexpected_test() // catch(int)
C++編譯器不必定對C++語言中函數異常規格說明進行支持。VC++編譯器不支持,G++編譯器支持。
C語言中,malloc函數申請內存失敗時返回NULL值。
C++語言中,對於早期的C++編譯器,new關鍵字申請內存失敗時,返回NULL值;對於現代C++編譯器,new關鍵字申請內存失敗時,拋出std::bad_alloc異常。
C++語言規範中,new關鍵字的標準行爲以下:
A、new在內存分配時,若是空間不足,會調用全局的new_handler函數,new_handler函數中拋出std::bad_alloc異常;若是成功,會在分配的空間調用構造函數建立對象,並返回對象的地址。
B、能夠自定義new_handler函數,處理默認new內存分配失敗的狀況。
#include <iostream> #include <cstdlib> using namespace std; void new_handler_test() { cout << "void new_handler_test()" << endl; cout << "No enough memory" << endl; exit(1); } int main(int argc, char *argv[]) { set_new_handler(new_handler_test); int* p = new(std::nothrow) int[10000000000]; return 0; } // output: // void new_handler_test() // No enough memory
上述代碼中,自定義new_handler函數,拋出異常時會調用。
#include <iostream> #include <cstdlib> #include <new> using namespace std; void new_handler_test() { cout << "void new_handler_test()" << endl; cout << "No enough memory" << endl; exit(1); } int main(int argc, char *argv[]) { new_handler func = set_new_handler(new_handler_test); cout << "func = " << func << endl; if(func) { try { func(); } catch(const bad_alloc& e) { cout << e.what() << endl; } } return 0; } // func = 0
上述代碼是在G++編譯器、VC++編譯器下編譯執行後打印的結果,代表G++編譯器、VC++編譯器沒有設置默認的new_handler函數。若是C++編譯器(如BCC編譯器)設置有默認的new_handler函數,func函數執行時將會拋出bad_alloc異常,被捕獲後打印出bad_alloc異常的相關信息。
不一樣的C++編譯器,new關鍵字申請動態內存失敗時表現不一樣。
工程實踐中,爲了在不一樣C++編譯器間統一new關鍵字的行爲,提升代碼的可移植性,解決方案以下:
A、從新定義全局的new/delete實現,不拋出任何異常;自定義new_handler函數,不拋出任何異常(不推薦)。
B、在類內重載new/delete操做符,不拋出任何異常。
C、單次動態內存分配時使用nothrow參數,指明new不拋出異常。
#include <iostream> #include <cstdlib> #include <new> using namespace std; class Test { int m_data; public: Test() { cout << "Test()" << endl; m_data = 0;//異常 } ~Test() { cout << "~Test()" << endl; } void* operator new (unsigned int size) { cout << "operator new: " << size << endl; // return malloc(size); return NULL; } void operator delete (void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[] (unsigned int size) { cout << "operator new[]: " << size << endl; // return malloc(size); return NULL; } void operator delete[] (void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; int main(int argc, char *argv[]) { Test* p = new Test(); cout << p << endl; delete p; return 0; } // output: // operator new: 4 // Test() // 異常
上述代碼在執行new操做符函數後會調用Test構造函數,並在初始化m_data成員變量時拋出異常。爲了確保不一樣C++編譯器在調用new關鍵字時具備相同的行爲,須要在new失敗時不拋出異常,所以須要在new操做符增長函數的異常聲明。
#include <iostream> #include <cstdlib> #include <new> using namespace std; class Test { int m_data; public: Test() { cout << "Test()" << endl; m_data = 0;//異常 } ~Test() { cout << "~Test()" << endl; } void* operator new (unsigned int size) throw() { cout << "operator new: " << size << endl; // return malloc(size); return NULL; } void operator delete (void* p) { cout << "operator delete: " << p << endl; free(p); } void* operator new[] (unsigned int size) throw() { cout << "operator new[]: " << size << endl; // return malloc(size); return NULL; } void operator delete[] (void* p) { cout << "operator delete[]: " << p << endl; free(p); } }; int main(int argc, char *argv[]) { Test* p = new Test(); cout << p << endl; delete p; p = new Test[5]; cout << p << endl; delete [] p; return 0; } // output: // operator new: 4 // 0 // operator new[]: 24 // 0
上述代碼對Test類的new和delete關鍵字進行了重載,統一了new失敗時的行爲。
#include <iostream> using namespace std; int main(int argc, char *argv[]) { //不拋出異常 int* p = new(nothrow) int[1000000000]; cout << "p = " << p << endl; delete [] p; int array[2] = {0}; struct Test { int x; int y; }; //在棧空間建立對象 Test* pTest = new(array) Test(); pTest->x = 100; pTest->y = 200; cout << array[0] << endl; cout << array[1] << endl; //顯示析構 pTest->~Test(); return 0; } // output: // p = 0 // 100 // 200
上述代碼中使用nothrow關鍵字對象new進行限制,確保new建立對象失敗時不會拋出異常。new關鍵字也能夠指定建立對象的地址空間,好比棧空間。
不是全部的C++編譯器都遵循C++標準規範,C++編譯器可能從新定義new關鍵字的實現,並在實現中拋出bad_alloc異常。VC++編譯器對new關鍵字進行了重定義,new關鍵字在new.cpp文件中進行了實現。
#ifdef _SYSCRT #include <cruntime.h> #include <crtdbg.h> #include <malloc.h> #include <new.h> #include <stdlib.h> #include <winheap.h> #include <rtcsup.h> #include <internal.h> void * operator new( size_t cb ) { void *res; for (;;) { // allocate memory block res = _heap_alloc(cb); // if successful allocation, return pointer to memory if (res) break; // call installed new handler if (!_callnewh(cb)) break; // new handler was successful -- try to allocate again } RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0)); return res; } #else /* _SYSCRT */ #include <cstdlib> #include <new> _C_LIB_DECL int __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc); _END_C_LIB_DECL void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc) { // try to allocate size bytes void *p; while ((p = malloc(size)) == 0) if (_callnewh(size) == 0) { // report no memory static const std::bad_alloc nomem; _RAISE(nomem); } return (p); } /* * Copyright (c) 1992-2002 by P.J. Plauger. ALL RIGHTS RESERVED. * Consult your license regarding permissions and restrictions. V3.13:0009 */ #endif /* _SYSCRT */
上述代碼顯示,在new失敗時默認拋出bad_alloc異常。