為什么需要異常?
- 異常機制的處理原理
程序會出現(xiàn)錯誤染突,尤其是不易察覺的錯誤匪傍。需要了解并解決這些錯誤。通常觉痛,程序出現(xiàn)錯誤役衡,都會強制退出,很難排除錯誤原因薪棒。
C語言如何表示錯誤
- 函數(shù)返回值
- 通常手蝎,成功返回
0
,返回值-1
榕莺。 - 返回值為指針類型,成功返回非
NULL
,失敗返回值NULL
棵介。
例如:malloc()
钉鸯;例外shmat()
失敗返回值為MAP_INVALD(-1)
- 其它另類的返回值
fread()
/fwrite()
返回讀寫字符長度size_t
,超出長度表示失敗邮辽。
- 通常手蝎,成功返回
- 全局變量
errno
異常處理特點
異常提供一個錯誤專用通道唠雕。
優(yōu)點:
- 不干擾正常的返回值。
- 必須處理異常吨述。
案例
通過命令行計算兩個數(shù)字相除岩睁。
#include <iostream>
#include <sstream>
using namespace std;
int main(int argc,char* argv[]){
istringstream iss(argv[1]); // 讀取第一個數(shù)字
int a(0);
iss >> a;
iss = argv[2]; // 讀取第二個數(shù)字
int b(0);
iss >> b;
cout<< a/b << endl;// 輸出數(shù)字相除
}
語法
異常分為兩個部分:拋出異常與捕獲并處理異常。
- 拋出異常
throw 表達式;
- 捕獲并處理異常
try {
// 保護代碼 包含可能拋出異常的語句揣云;
} catch (類型名 [形參名]) {
// catch塊 處理異常
}
特點
- 只要拋出異常捕儒,異常后的代碼不再執(zhí)行。
- 異常的所拋出與經(jīng)過的棧都會銷毀邓夕。
異常機制
try
-throw
-catch
的目標是問題檢測與問題解決分離
復雜一點地寫法
try {
// 保護代碼 包含可能拋出異常的語句刘莹;
} catch (類型名1 [形參名]) {
// catch塊 處理異常
} catch (類型名2 [形參名]) {
// catch塊 處理異常
} catch (類型名3 [形參名]) {
// catch塊 處理異常
} catch(...){
// catch塊 處理異常
}
注意
- 異常捕獲具有類型匹配,只有相同的或者父類類型才能匹配到焚刚。
- 如果多個
catch
都能接受相同異常点弯,只有最前面的一個可以接收到。
catch(...)
只能放在所有異常捕獲的最后
異常的接口聲明/異常規(guī)范
返回值類型 函數(shù)() throw(異常列表);
指定函數(shù)可以拋出何種異常矿咕,如果沒有throw(異常列表)
默認可以拋出所有異常抢肛。
指定函數(shù)不拋出函數(shù),異常列表為空throw()
痴腌。
那么當異常拋出后新對象如何釋放雌团?
異常處理機制保證:異常拋出的新對象并非創(chuàng)建在函數(shù)棧上燃领,而是創(chuàng)建在專用的異常棧上士聪,因此它才可以跨接多個函數(shù)而傳遞到上層,否則在棧清空的過程中就會被銷毀猛蔽。所有從try
到throw
語句之間構造起來的對象的析構函數(shù)將被自動調(diào)用剥悟。但如果一直上溯到main
函數(shù)后還沒有找到匹配的catch
塊,那么系統(tǒng)調(diào)用terminate()終止整個程序曼库,這種情況下不能保證所有局部對象會被正確地銷毀区岗。
舉例
- 捕獲異常
#include <iostream>
using namespace std;
void test(){
cout << "before throw." << endl;
throw -1;
cout << "after throw." << endl;
}
int main(){
try{
test();
}catch(int a){
cout << "exception:" << a << endl;
}
}
- 異常與局部對象析構
#include <iostream>
using namespace std;
class Test{
public:
Test(){
cout << "Test Init" <<endl;
}
~Test(){
cout << "Test Destroy" <<endl;
}
};
int main(){
try{
Test t;
cout << "before throw." << endl;
throw -1;
cout << "after throw." << endl;
}catch(int a){
cout << "exception:" << a << endl;
}
}
注意事項
- 如果拋出的異常一直沒有函數(shù)捕獲(catch),則會一直上傳到c++運行系統(tǒng)那里毁枯,導致整個程序的終止慈缔。
- 一般在異常拋出后資源可以正常被釋放,但注意如果在類的構造函數(shù)中拋出異常种玛,系統(tǒng)是不會調(diào)用它的析構函數(shù)的藐鹤,處理方法是:如果在構造函數(shù)中要拋出異常瓤檐,則在拋出前要記得刪除申請的資源。
- 異常處理僅僅通過類型而不是通過值來(switch-case)匹配的娱节,所以catch塊的參數(shù)可以沒有參數(shù)名稱挠蛉,只需要參數(shù)類型。
- 函數(shù)原型中的異常說明要與實現(xiàn)中的異常說明一致肄满,否則容易引起異常沖突谴古。
- 應該在throw語句后寫上異常對象時,throw先通過Copy構造函數(shù)構造一個新對象稠歉,再把該新對象傳遞給 catch.
- catch塊的參數(shù)推薦采用地址傳遞而不是值傳遞掰担,不僅可以提高效率,還可以利用對象的多態(tài)性轧抗。另外恩敌,派生類的異常撲獲要放到父類異常撲獲的前面,否則横媚,派生類的異常無法被撲獲纠炮。
- 編寫異常說明時,要確保派生類成員函數(shù)的異常說明和基類成員函數(shù)的異常說明一致灯蝴,即派生類改寫的虛函數(shù)的異常說明至少要和對應的基類虛函數(shù)的異常說明相同芹啥,甚至更加嚴格,更特殊异逐。
標準異常類
exception
派生
異常類 | 作用 |
---|---|
logic_error |
邏輯錯誤,在程序運行前可以檢測出來 |
runtime_error |
運行時錯誤,僅在程序運行中檢測到 |
邏輯異常logic_error
派生
異常類 | 作用 |
---|---|
domain_error |
違反了前置條件 |
invalid_argument |
指出函數(shù)的一個無效參數(shù) |
length_error |
指出有一個超過類型size_t 的最大可表現(xiàn)值長度的對象的企圖 |
out_of_range |
參數(shù)越界 |
bad_cast |
在運行時類型識別中有一個無效的dynamic_cast 表達式 |
bad_typeid |
報告在表達試typeid(*p) 中有一個空指針p
|
運行時runtime_error
派生
異常類 | 作用 |
---|---|
range_error |
違反后置條件 |
bad_alloc |
存儲分配錯誤 |
嘗試捕獲邏輯異常和運行時異常
自定義異常類
- 編碼流程
1.繼承異常類exception
2.實現(xiàn)接口what()
- 代碼結構
class 異常類:public exception {
public:
const char* what()const throw() {
return 信息字符串;
}
};
構造函數(shù)汉嗽、析構函數(shù)的異常處理
- 構造函數(shù)可以拋出異常,此時不會調(diào)用析構函數(shù)问潭,所以如果拋出異常前猿诸,申請了資源,需要自己釋放狡忙。
- C++標準指明析構函數(shù)不能梳虽、也不應該拋出異常。
- C++標準規(guī)定灾茁,構造函數(shù)失敗窜觉,析構函數(shù)不會執(zhí)行。就是說在構造函數(shù)拋出異常前分配的資源將無法釋放北专。
- 如果析構函數(shù)拋出異常禀挫,則異常點之后的程序不會執(zhí)行,如果析構函數(shù)在異常點之后執(zhí)行了某些必要的動作比如釋放某些資源拓颓,則這些動作不會執(zhí)行语婴,會造成諸如資源泄漏的問題。
- 通常異常發(fā)生時,c++的機制會調(diào)用已經(jīng)構造對象的析構函數(shù)來釋放資源砰左,此時若析構函數(shù)本身也拋出異常画拾,則前一個異常尚未處理,又有新的異常菜职,會造成程序崩潰的問題青抛。
是否使用異常機制
為什么很多經(jīng)典書籍鼓勵使用異常,但是實際開發(fā)中很多C++編碼規(guī)范卻禁用異常酬核?
C++異常機制在語法上是更加優(yōu)雅的處理錯誤蜜另,但是實際上編譯出來的程序會有一些性能損失,另外錯誤地使用異常處理代碼會變得更加復雜嫡意。
編譯器選項
g++
特殊編譯選項g++ -fno-exceptions
在不同的編碼規(guī)范中,對是否使用異常存在爭議蔬螟。