13、c++異常處理
- 拋出異常
- 捕獲異常
- 未拋出異常時的流程
- 拋出異常時的流程
- 捕獲異常
- 匹配順序
- 異常說明
- 異常處理
- 構造函數中的異常
- 析構函數中的異常
- 標準庫異常類
拋出異常
- throw 異常對象
- 可以拋出基本類型的對象,如:
throw -1;throw "內存分配失敗!"
; - 也可以拋出類類型的對象,如:
MemoryException ex;throw ex;throw MemoryException();
- 但不要拋出局部對象的指針,如:
MemoryException ex;throw &ex;
// 錯誤!
捕獲異常
try {
可能引發異常的語句
}
catch (異常類型1& ex){針對異常類型1的異常處理;}
catch (異常類型2& ex) {針對異常類型2的異常處理;}
...
catch (異常類型n& ex) {針對異常類型n的異常處理;}
未拋出異常時的流程
拋出異常時的流程
捕獲異常
- 建議在catch子句中使用引用接收異常對象,避免因為拷貝構造帶來性能損失
- 推薦以匿名臨時對象的形式拋出異常
- 異常對象必須允許被拷貝構造和析構
匹配順序
根據異常對象的類型自上至下順序匹配,而非最優匹配,因此對子類類型異常的捕獲不要放在對基類類型異常的捕獲后面
異常說明
異常說明是函數原型的一部分,旨在說明函數可能拋出的異常類型
返回類型 函數名(形參表) throw (異常類型1,異常類型2,...) {函數體;}
異常說明是一種承諾,承諾函數不會拋出異常說明以外的異常類型
如果函數拋出了異常說明以外的異常類型,那么該異常將無法被捕獲并導致進程中止
隱式拋出異常的函數也可以列出它的異常說明
異常說明可以沒有也可以為空
- 沒有異常說明,表示可能拋出任何類型的異常
void foo (void) { ... }
- 異常說明為空,表示不會拋出任何類型的異常
void foo (void) throw () { ... }
異常說明在函數的聲明和定義中必須保持嚴格一致,否則將導致編譯錯誤
異常處理
- 可以拋出基本類型的異常
- 可以拋出類類型的異常
- 利用類類型的異常,攜帶更多診斷信息,以便查錯
- 可以在catch塊中繼續拋出所捕獲的異常,或其它異常
- 任何未被捕獲的異常,默認的處理方式就是中止進程
- 忽略異常,不做處理
構造函數中的異常
構造函數可以拋出異常,某些時候還必須拋出異常
- 構造過程中可能遇到各種錯誤,比如內存分配失敗
- 構造函數沒有返回值,無法通過返回值通知調用者
構造函數拋出異常,對象將被不完整構造,而一個被不完整構造的對象,其析構函數永遠不會被執行
- 所有對象形式的成員變量,在拋出異常的瞬間,都能得到正確地析構 (構造函數的回滾機制)
- 所有動態分配的資源,必須在拋出異常之前,自己手動釋放,否則將形成資源的泄漏
析構函數中的異常
不要從析構函數中主動拋出異常
在兩種情況下,析構函數會被調用
- 正常銷毀對象,離開作用域或顯式delete
- 在異常傳遞的堆棧輾轉開解(stack-unwinding)過程中
對于第二種情況,異常正處于激活狀態,而析構函數又拋出了異常這時C++將通過std::terminate()函數,令進程中止
對于可能引發異常的操作,盡量在析構函數內部消化
try {... }catch (...) { ... }
標準庫異常類
// 利用return報告異常信息
#include <iostream>
#include <cstdio>
using namespace std;class A{
public:A() { cout << "A()被調用" << endl; }~A(){ cout << "~A()被調用" << endl; }
};void foo(){cout << "foo出錯前的幾百行代碼" << endl;A a;FILE* pfile = fopen("./cfg","r");if(!pfile)throw -1;cout << "foo出錯后的幾百行代碼" << endl;
} // a.~A() 釋放a本身所占內存空間void bar(){cout << "bar出錯前的幾百行代碼" << endl;A b;
// try{foo();
// }
// catch(int e){
// cout << "bar函數中捕獲異常信息: " << e << endl;
// }cout << "bar出錯后的幾百行代碼" << endl;
} // b.~A() 釋放b本身所占內存空間void hum(){cout << "hum出錯前的幾百行代碼" << endl;A c;try{bar();}catch(int e){cout << "hum函數中捕獲異常信息:" << e << endl;}cout << "bar出錯后的幾百行代碼" << endl;
} // c.~A() 釋放c本身所占內存空間int main( void ){cout << "main出錯前的幾百行代碼" << endl;A d;hum();cout << "main出錯后的幾百行代碼" << endl;return 0;
} // d.~A() 釋放d本身所占內存空間
// 建議將子類類型異常捕獲放到基類類型異常捕獲的前面
#include <iostream>
using namespace std;class A{};class B : public A{};void foo(){throw B();
}int main( void ){try{foo();}catch(B& b){ cout << "B類型catch捕獲" << endl;}catch(A& a){ cout << "A類型catch捕獲" << endl;}return 0;
}
// 沒有異常說明和異常說明為空
#include <iostream>
using namespace std;void foo() { // 沒有異常說明:函數內部可能拋出任何類型的異常throw "Hello world!"; // 3.14; // -1;
}void bar() throw(){ // 異常說明為空:承諾函數內部絕對不會拋出任何類型的異常
// throw -1;
}
// 異常說明在聲明和定義時必須嚴格一致,否則將報編譯錯誤
void hum() throw(int,double); // 聲明
void hum() throw(int,double){ // 定義
}int main( void ){try{foo();
// bar();}catch( ... ){ // 忽略異常// ...}/* catch(int& e){cout << "1. 捕獲異常信息:"<< e << endl; }catch(double& e){cout << "2. 捕獲異常信息:" << e << endl;}catch(const char* e){cout << "3. 捕獲異常信息:" << e << endl;}*/return 0;
}
// 構造函數中的異常
#include <iostream>
#include <cstdio>
using namespace std;class A{
public:A(){ cout << "A()被調用" << endl; }~A(){ cout << "~A()被調用" << endl; }
};
class C{
public:C():m_p(new int){//【A m_a;】定義m_a,利用m_a.A()//【int* m_p = new int;】定義m_p,初值指向一塊堆內存(動態資源)cout << "C()被調用" << endl;FILE* pfile = fopen("./cfg","r");if(!pfile){delete m_p; // 需要自己手動釋放// 對于m_a.利用m_a.~A()// 釋放m_a/m_p本身所占內存空間throw -1;}// ....構造函數中后續代碼...}~C(){delete m_p;cout << "~C()被調用" << endl;// 對于m_a.利用m_a.~A()// 釋放m_a/m_p本身所占內存空間}
private:A m_a;int* m_p;
};int main( void ){try{C c; // 定義c,利用c.C();}// 如果c是完整構造對象,將利用c.~C(),但是如果c是殘缺對象,就不會調用~C()catch( ... ){// ...}return 0;
}