為什么需要異常?
- 異常機(jī)制的處理原理
程序會(huì)出現(xiàn)錯(cuò)誤,尤其是不易察覺(jué)的錯(cuò)誤。需要了解并解決這些錯(cuò)誤。通常,程序出現(xiàn)錯(cuò)誤,都會(huì)強(qiáng)制退出,很難排除錯(cuò)誤原因。
C語(yǔ)言如何表示錯(cuò)誤
- 函數(shù)返回值
- 通常,成功返回
0
,返回值-1
。 - 返回值為指針類型,成功返回非
NULL
,失敗返回值NULL
。
例如:malloc()
;例外shmat()
失敗返回值為MAP_INVALD(-1)
- 其它另類的返回值
fread()
/fwrite()
返回讀寫字符長(zhǎng)度size_t
,超出長(zhǎng)度表示失敗。
- 通常,成功返回
- 全局變量
errno
異常處理特點(diǎn)
異常提供一個(gè)錯(cuò)誤專用通道。
優(yōu)點(diǎn):
- 不干擾正常的返回值。
- 必須處理異常。
案例
通過(guò)命令行計(jì)算兩個(gè)數(shù)字相除。
#include <iostream>
#include <sstream>
using namespace std;
int main(int argc,char* argv[]){
istringstream iss(argv[1]); // 讀取第一個(gè)數(shù)字
int a(0);
iss >> a;
iss = argv[2]; // 讀取第二個(gè)數(shù)字
int b(0);
iss >> b;
cout<< a/b << endl;// 輸出數(shù)字相除
}
語(yǔ)法
異常分為兩個(gè)部分:拋出異常與捕獲并處理異常。
- 拋出異常
throw 表達(dá)式;
- 捕獲并處理異常
try {
// 保護(hù)代碼 包含可能拋出異常的語(yǔ)句;
} catch (類型名 [形參名]) {
// catch塊 處理異常
}
特點(diǎn)
- 只要拋出異常,異常后的代碼不再執(zhí)行。
- 異常的所拋出與經(jīng)過(guò)的棧都會(huì)銷毀。
異常機(jī)制
try
-throw
-catch
的目標(biāo)是問(wèn)題檢測(cè)與問(wèn)題解決分離
復(fù)雜一點(diǎn)地寫法
try {
// 保護(hù)代碼 包含可能拋出異常的語(yǔ)句;
} catch (類型名1 [形參名]) {
// catch塊 處理異常
} catch (類型名2 [形參名]) {
// catch塊 處理異常
} catch (類型名3 [形參名]) {
// catch塊 處理異常
} catch(...){
// catch塊 處理異常
}
注意
- 異常捕獲具有類型匹配,只有相同的或者父類類型才能匹配到。
- 如果多個(gè)
catch
都能接受相同異常,只有最前面的一個(gè)可以接收到。
catch(...)
只能放在所有異常捕獲的最后
異常的接口聲明/異常規(guī)范
返回值類型 函數(shù)() throw(異常列表);
指定函數(shù)可以拋出何種異常,如果沒(méi)有throw(異常列表)
默認(rèn)可以拋出所有異常。
指定函數(shù)不拋出函數(shù),異常列表為空throw()
。
那么當(dāng)異常拋出后新對(duì)象如何釋放?
異常處理機(jī)制保證:異常拋出的新對(duì)象并非創(chuàng)建在函數(shù)棧上,而是創(chuàng)建在專用的異常棧上,因此它才可以跨接多個(gè)函數(shù)而傳遞到上層,否則在棧清空的過(guò)程中就會(huì)被銷毀。所有從try
到throw
語(yǔ)句之間構(gòu)造起來(lái)的對(duì)象的析構(gòu)函數(shù)將被自動(dòng)調(diào)用。但如果一直上溯到main
函數(shù)后還沒(méi)有找到匹配的catch
塊,那么系統(tǒng)調(diào)用terminate()終止整個(gè)程序,這種情況下不能保證所有局部對(duì)象會(huì)被正確地銷毀。
舉例
- 捕獲異常
#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;
}
}
- 異常與局部對(duì)象析構(gòu)
#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;
}
}
注意事項(xiàng)
- 如果拋出的異常一直沒(méi)有函數(shù)捕獲(catch),則會(huì)一直上傳到c++運(yùn)行系統(tǒng)那里,導(dǎo)致整個(gè)程序的終止。
- 一般在異常拋出后資源可以正常被釋放,但注意如果在類的構(gòu)造函數(shù)中拋出異常,系統(tǒng)是不會(huì)調(diào)用它的析構(gòu)函數(shù)的,處理方法是:如果在構(gòu)造函數(shù)中要拋出異常,則在拋出前要記得刪除申請(qǐng)的資源。
- 異常處理僅僅通過(guò)類型而不是通過(guò)值來(lái)(switch-case)匹配的,所以catch塊的參數(shù)可以沒(méi)有參數(shù)名稱,只需要參數(shù)類型。
- 函數(shù)原型中的異常說(shuō)明要與實(shí)現(xiàn)中的異常說(shuō)明一致,否則容易引起異常沖突。
- 應(yīng)該在throw語(yǔ)句后寫上異常對(duì)象時(shí),throw先通過(guò)Copy構(gòu)造函數(shù)構(gòu)造一個(gè)新對(duì)象,再把該新對(duì)象傳遞給 catch.
- catch塊的參數(shù)推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對(duì)象的多態(tài)性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無(wú)法被撲獲。
- 編寫異常說(shuō)明時(shí),要確保派生類成員函數(shù)的異常說(shuō)明和基類成員函數(shù)的異常說(shuō)明一致,即派生類改寫的虛函數(shù)的異常說(shuō)明至少要和對(duì)應(yīng)的基類虛函數(shù)的異常說(shuō)明相同,甚至更加嚴(yán)格,更特殊。
標(biāo)準(zhǔn)異常類
exception
派生
異常類 | 作用 |
---|---|
logic_error |
邏輯錯(cuò)誤,在程序運(yùn)行前可以檢測(cè)出來(lái) |
runtime_error |
運(yùn)行時(shí)錯(cuò)誤,僅在程序運(yùn)行中檢測(cè)到 |
邏輯異常logic_error
派生
異常類 | 作用 |
---|---|
domain_error |
違反了前置條件 |
invalid_argument |
指出函數(shù)的一個(gè)無(wú)效參數(shù) |
length_error |
指出有一個(gè)超過(guò)類型size_t 的最大可表現(xiàn)值長(zhǎng)度的對(duì)象的企圖 |
out_of_range |
參數(shù)越界 |
bad_cast |
在運(yùn)行時(shí)類型識(shí)別中有一個(gè)無(wú)效的dynamic_cast 表達(dá)式 |
bad_typeid |
報(bào)告在表達(dá)試typeid(*p) 中有一個(gè)空指針p
|
運(yùn)行時(shí)runtime_error
派生
異常類 | 作用 |
---|---|
range_error |
違反后置條件 |
bad_alloc |
存儲(chǔ)分配錯(cuò)誤 |
嘗試捕獲邏輯異常和運(yùn)行時(shí)異常
自定義異常類
- 編碼流程
1.繼承異常類exception
2.實(shí)現(xiàn)接口what()
- 代碼結(jié)構(gòu)
class 異常類:public exception {
public:
const char* what()const throw() {
return 信息字符串;
}
};
構(gòu)造函數(shù)、析構(gòu)函數(shù)的異常處理
- 構(gòu)造函數(shù)可以拋出異常,此時(shí)不會(huì)調(diào)用析構(gòu)函數(shù),所以如果拋出異常前,申請(qǐng)了資源,需要自己釋放。
- C++標(biāo)準(zhǔn)指明析構(gòu)函數(shù)不能、也不應(yīng)該拋出異常。
- C++標(biāo)準(zhǔn)規(guī)定,構(gòu)造函數(shù)失敗,析構(gòu)函數(shù)不會(huì)執(zhí)行。就是說(shuō)在構(gòu)造函數(shù)拋出異常前分配的資源將無(wú)法釋放。
- 如果析構(gòu)函數(shù)拋出異常,則異常點(diǎn)之后的程序不會(huì)執(zhí)行,如果析構(gòu)函數(shù)在異常點(diǎn)之后執(zhí)行了某些必要的動(dòng)作比如釋放某些資源,則這些動(dòng)作不會(huì)執(zhí)行,會(huì)造成諸如資源泄漏的問(wèn)題。
- 通常異常發(fā)生時(shí),c++的機(jī)制會(huì)調(diào)用已經(jīng)構(gòu)造對(duì)象的析構(gòu)函數(shù)來(lái)釋放資源,此時(shí)若析構(gòu)函數(shù)本身也拋出異常,則前一個(gè)異常尚未處理,又有新的異常,會(huì)造成程序崩潰的問(wèn)題。
是否使用異常機(jī)制
為什么很多經(jīng)典書籍鼓勵(lì)使用異常,但是實(shí)際開(kāi)發(fā)中很多C++編碼規(guī)范卻禁用異常?
C++異常機(jī)制在語(yǔ)法上是更加優(yōu)雅的處理錯(cuò)誤,但是實(shí)際上編譯出來(lái)的程序會(huì)有一些性能損失,另外錯(cuò)誤地使用異常處理代碼會(huì)變得更加復(fù)雜。
編譯器選項(xiàng)
g++
特殊編譯選項(xiàng)g++ -fno-exceptions
在不同的編碼規(guī)范中,對(duì)是否使用異常存在爭(zhēng)議。