概要 |
異常是程序執行期產生問題,比如嘗試除以零的操作。 異常提供了一種轉移程序控制權的方式。C++ 異常處理涉及到三個關鍵字:try、catch、throw。
使用 try/catch 語句的語法: try { // 保護代碼 } catch( ExceptionName e1 ) { // catch 塊 } catch( ExceptionName e2 ) { // catch 塊 } catch( ExceptionName eN ) { // catch 塊 } 如果 try 塊在不同場景拋出不同異常,此時可嘗試羅列多個 catch 語句,用於捕獲不同類型異常。 |
|||||||||||||
拋出異常
|
使用 throw 語句在代碼塊中任何位置拋出異常。throw 語句的操作數可以是任意表達式,表達式的結果類型決定了拋出異常的類型。 示例: double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); } |
|||||||||||||
捕獲異常
|
catch 塊跟在 try 塊后面,用於捕獲異常。可指定想要捕捉的異常類型,由 catch 關鍵字后括號內的異常聲明類型決定。 try { // 保護代碼 } catch( ExceptionName e ) { // 處理 ExceptionName 異常的代碼 } 上面的代碼會捕獲一個類型為 ExceptionName 的異常。如果您想讓 catch 塊能夠處理 try 塊拋出的任何類型的異常,則必須在異常聲明的括號內使用省略號 ...,如下所示: try { // 保護代碼 } catch(...) { // 能處理任何異常的代碼 } 下面是一個實例,拋出一個除以零的異常,並在 catch 塊中捕獲該異常。 實例 #include <iostream> using namespace std; double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); } int main () { int x = 50; int y = 0; double z = 0; try { z = division(x, y); cout << z << endl; } catch (const char* msg) { cerr << msg << endl; } return 0; } 此代碼拋出了一個類型為 const char* 的異常,因此捕獲異常時,須在 catch 塊中使用 const char*。以上代碼被編譯和執行時,結果如下: Division by zero condition! |
|||||||||||||
C++ 標准異常 |
C++ 提供一系列標准異常,定義在 <exception> 中,我們可以在程序中使用這些標准的異常。它們是以父子類層次結構組織起來的,如下所示:
std::exception 該異常是所有標准 C++ 異常的父類。 std::bad_alloc 該異常可以通過 new 拋出。 std::bad_cast 該異常可以通過 dynamic_cast 拋出。 std::bad_exception 這在處理 C++ 程序中無法預期的異常時非常有用。 std::bad_typeid 該異常可以通過 typeid 拋出。 std::logic_error 理論上可以通過讀取代碼來檢測到的異常。 std::domain_error 當使用了一個無效的數學域時,會拋出該異常。 std::invalid_argument 當使用了無效的參數時,會拋出該異常。 std::length_error 當創建了太長的 std::string 時,會拋出該異常。 std::out_of_range 該異常可以通過方法拋出,例如 std::vector 和 std::bitset<>::operator[]()。 std::runtime_error 理論上不可以通過讀取代碼來檢測到的異常。 std::overflow_error 當發生數學上溢時,會拋出該異常。 std::range_error 當嘗試存儲超出范圍的值時,會拋出該異常。 std::underflow_error 當發生數學下溢時,會拋出該異常。 |
|||||||||||||
自定義異常 |
通過繼承和重載 exception 類來定義新的異常。示例: #include <iostream> #include <exception> using namespace std; struct MyException : public exception { const char * what () const throw () { return "C++ Exception"; } }; int main() { try { throw MyException(); } catch(MyException& e) { std::cout << "MyException caught" << std::endl; std::cout << e.what() << std::endl; } catch(std::exception& e) { //其他的錯誤 } } 這將產生以下結果: MyException caught C++ Exception 注解:
示例: void fun() throw() 表示fun不允許拋出任何異常,即fun是異常安全的。 void fun() throw(...) 表示fun可以拋出任何形式的異常。 void fun() throw(exceptionType) 表示fun只能拋出exceptionType類型的異常。 |
|||||||||||||
noexcept關鍵字 (c++11) |
動態異常聲明thro由於很少使用,在c++11中被棄用, 表示函數不會拋出異常的動態異常聲明throw()也被新的noexcept異常聲明取代, noexcept修飾的函數不會拋出異常, 在c++11中,如果noexcept修飾的函數拋出了異常,編譯器可以選擇直接調用std::terminate() 來終止程序的運行,這比基於異常機制的throw()在效率上會高出一些。使用noexcept可有效阻止異常的傳播與擴散 #include <iostream> using namespace std; void throw_(){ throw 1; } void NoBlockThrow(){ throw_(); } void BlockThrow() noexcept { throw_(); } int main() { /*try { throw_(); } catch (...) { cout << "found throw." << endl; } try { NoBlockThrow(); } catch (...){ cout << "throw is not blocked" << endl; }*/ try{ BlockThrow(); } catch (...){ cout << "found throw 1" << endl; } //noexcept修飾的函數拋出異常,編譯器直接調用std::terminate()終止程序運行, } 這個程序運行時,前兩個try catch會正常輸出xx到控制台,最后一個try-catch塊,程序會彈出錯誤框表示程序已被終止,這是因為如果noexcept修飾的函數拋出了異常,那么編譯器就直接調用std::terminate()終止了程序的運行。 |
|||||||||||||
異常嵌套處理 |
當處理第1個異常時,可能會觸發第2種異常情況,從而要求拋出第2個異常,但問題是當拋出第2個異常時,正在處理的第1個異常信息會丟失。C++用嵌套異常(nested exception)的概念提供了解決這一問題的方案,嵌套異常允許將捕獲的異常嵌套到新的異常環境。使用std::throw_nested()可以拋出嵌套了異常的異常。第2個異常的catch處理程序可以使用dynamic_cast訪問代表第一個異常的nested_exception。 下面的示例演示了嵌套異常的用法。 示例定義了一個從exception派生的MyExcepion類,其構造函數接受一個字符串。 #pragma once #include<exception> #include<iostream> #include<string> using namespace std; class MyException :public exception { public: MyException(const char* msg) :mMsg(msg) {} virtual ~MyException() noexcept {} virtual const char* what() const noexcept override { return mMsg.c_str(); } private: string mMsg; }; 當處理第一個異常,且需要拋出嵌套了第一個異常的第二個異常時,需要使用std::throw_with_nested()函數。下面的doSomething()函數排除一個runtime_error異常,這個異常立即被處理程序捕獲。捕獲處理程序編寫了一條消息,然后使用throw_with_nested()函數拋出第二個異常,第一個異常嵌套在其中。注意嵌套異常時自動實現的。 void doSomething() { try { throw runtime_error("Throwing a runtime_error exception"); } catch (const runtime_error&e) { cout << __func__ << " caught a runtime_error" << std::endl; cout << __func__ << " throwing MyException" << endl; throw_with_nested(MyException("MyException with nested runtime_error)")); } 下面的main()函數演示如何處理嵌套異常。該段代碼調用了doSomething()函數,還有一個處理MyException類型異常的處理程序,當捕獲到這類異常時,會編寫一條消息,然后使用dynamic_cast訪問嵌套的異常。如果內部沒有嵌套異常,結果為空指針。如果存在嵌套異常,調用nest_exception的rethrow_nested()方法。這樣會再次拋出嵌套的異常,這一異常可以在另一個try/catch塊中捕獲。 int main() { try { doSomething(); } catch (const MyException&e) { cout << __func__ << " caught MyException: " << e.what() << endl; const nested_exception *pNested = dynamic_cast<const nested_exception*>(&e); if (pNested) { try { pNested->rethrow_nested(); } catch (const std::runtime_error& e) { //handle nested exception cout << " Nested exception: " << e.what() << endl; } } } return 0; } 輸出結果: doSomething caught a runtime_error doSomthing throwing My Exception main caught MyException: MyException with nested runtime_error> Nested exception: Throwing a runtime_error exception |