異常的拋出
在C++中分飞,通過throw一個表達式來引發(fā)異常,被拋出的表達式的類型以及當前的調(diào)用鏈共同決定了哪段處理代碼(handler)被用來處理異常。被選中的處理代碼是在調(diào)用鏈中與拋出對象類型匹配的最近的處理代碼。
執(zhí)行throw時钉稍,throw后面的語句不會再執(zhí)行,轉(zhuǎn)而去執(zhí)行與之匹配的catch塊棺耍,該catch塊可能是同一個函數(shù)贡未,也可能位于調(diào)用了發(fā)生異常的函數(shù)的另一個函數(shù)中。
C++沒有finally蒙袍,只有Windows下的__finally俊卤。
棧展開
當throw時,首先會檢查是否位于try塊中害幅,是的話檢查與try關(guān)聯(lián)的catch塊消恍,如果找到了匹配的catch塊,就使用該catch處理異常以现,找不到則檢查外層try塊是否有匹配的catch塊狠怨,還是找不到則檢查調(diào)用當前函數(shù)的外層函數(shù)是否有匹配的catch塊佩抹,如果一直找不到,程序?qū)⑼顺鋈《@個過程被稱為棧展開。
在棧展開過程中无宿,位于調(diào)用鏈上的語句塊有可能提前退出茵汰,編譯器將負責(zé)確保在這些語句塊中創(chuàng)建的局部對象能正確釋放。
由于在棧展開過程中出于釋放局部對象可能調(diào)用析構(gòu)函數(shù)的考慮孽鸡,析構(gòu)函數(shù)不應(yīng)該拋出不能被它自身處理的異常蹂午,換句話說,如果析構(gòu)函數(shù)需要執(zhí)行某個可能拋出異常的操作彬碱,則該操作應(yīng)該被放置在try塊中自己處理掉豆胸。標準庫類型一般都能確保它們的析構(gòu)函數(shù)不會引發(fā)異常。
棧展開退出某個塊或者函數(shù)時巷疼,會同時釋放局部對象晚胡,因此throw不能拋出一個指向局部對象的指針。
執(zhí)行完catch塊后嚼沿,會找到與try塊關(guān)聯(lián)的最后一個catch塊之后的點繼續(xù)執(zhí)行估盘。
未處理的異常
如果找不到處理異常的代碼,程序會調(diào)用terminate骡尽,terminate會調(diào)用abort退出程序遣妥,你可以通過set_terminate執(zhí)行其它操作而不是調(diào)用abort。
void terminateHandler() {
std::cout << "terminateHandler was called by terminate." << std::endl;
system("pause");
exit(-1);
}
int main()
{
set_terminate(terminateHandler);
throw std::exception();
system("pause");
return 0;
}
捕獲異常
catch(params list)子句用來捕獲異常攀细,如果catch無需訪問異常對象箫踩,則我們可以忽略形參名。聲明的類型決定了處理代碼所能捕獲的異常類型(使用catch(...)能捕獲所有異常)谭贪,這個類型必須是完全類型(不能只聲明境钟,不定義),可以是左值引用故河,但不能是右值引用吱韭。如果參數(shù)是引用類型,則該參數(shù)是異常對象的一個別名鱼的,反之該參數(shù)是異常對象的一個副本理盆。為避免不必要的異常對象復(fù)制和對象切片, catch 子句的最佳實踐是按引用捕捉凑阶。
異常類型也能是基本類型猿规,例如int,char*
int errorFunc()
{
try{
throw "error";
}
catch (const char* errorStr){
std::cout << errorStr << std::endl;
}
return 0;
}
int main()
{
errorFunc();
system("pause");
return 0;
}
由于只有第一個與異常匹配的catch塊會被執(zhí)行宙橱,所以越是專門的catch塊越應(yīng)該置于整個catch列表的前端姨俩,派生類異常的處理代碼應(yīng)該出現(xiàn)基類異常處理代碼之前蘸拔。
有時一個單獨的catch塊不能完整地處理異常,這個時候可以讓調(diào)用鏈上更上一層的函數(shù)接著處理異常环葵,方法是在catch塊中執(zhí)行throw;(不包含任何表達式)调窍。如果在throw前修改了參數(shù),則只有當參數(shù)是引用類型時张遭,我們對參數(shù)所做的改變才會被保留并繼續(xù)傳播邓萨。
處理構(gòu)造函數(shù)初始值列表拋出的異常
由于初始值列表拋出異常時,函數(shù)體內(nèi)的try語句塊還未生效菊卷,所以我們必須把構(gòu)造函數(shù)寫成函數(shù)try語句塊缔恳,代碼如下:
class Person
{
public:
int height_;
Person() try :height_(10)
{
//...
}
catch (...) {
}
};
noexcept說明符
在C++11中,我們可以通過noexcept說明某個函數(shù)不會拋出異常洁闰,形式是關(guān)鍵字noexcept緊跟在函數(shù)的參數(shù)列表后面歉甚。
void func1() noexcept;//不會拋出異常
void func2()//可能拋出異常
noexcept說明符要么出現(xiàn)在函數(shù)的所有聲明和定義語句中,要么一次也不出現(xiàn)扑眉。我們也可以在函數(shù)指針的聲明和定義中指定noexcept纸泄。在typedef或類型別名中不能出現(xiàn)noexcept。在成員函數(shù)中腰素,noexcept說明符需要跟在const及引用限定符之后刃滓,而在final,override或虛函數(shù)的=0之前耸弄。
如果一個函數(shù)拋出了異常咧虎,但是又有noexcept說明符,編譯器并不會報錯计呈,只是警告而已砰诵。一旦一個noexcept函數(shù)拋出了異常,程序就會調(diào)用terminate以確保遵守不在運行時拋出異常的承諾捌显。因此noexcept用于兩種情況下茁彭,一種是我們確認函數(shù)不會拋出異常,二是我們根本不知道如何處理該異常扶歪。
noexcept說明符接受一個可選的實參理肺,該實參必須能轉(zhuǎn)換為bool類型,如果實參為true善镰,則不會拋出異常妹萨。
void func1() noexcept(true);//不會拋出異常
void func2() noexcept(false);//可能拋出異常
noexcept運算符
noexcept運算符經(jīng)常與noexcept說明符的實參混合使用,noexcept(function(...))是一個一元運算符炫欺,它的返回值是一個bool類型的右值表達式乎完,用于表達給定的表達式是否會拋出異常。
void func1() noexcept(true);//不會拋出異常
void func2() noexcept(noexcept(func1()));//不會拋出異常
noexcept與函數(shù)指針品洛,虛函數(shù)和拷貝控制
函數(shù)指針以及該指針所指的函數(shù)必須具有一致的異常說明树姨,如果某個函數(shù)指針做了不拋出異常的聲明摩桶,那么該指針只能指向不拋出異常的函數(shù),相反的如果函數(shù)指針說明了可能拋出異常帽揪,則該指針可以指向任何函數(shù)硝清。
如果一個虛函數(shù)說明它不會拋出異常,則派生類的虛函數(shù)也必須做出同樣的說明转晰,相反虛函數(shù)說明了可能拋出異常耍缴,派生類對應(yīng)的函數(shù)既可以允許拋出異常也可以不允許。
當編譯器合成拷貝控制成員時挽霉,同時也生成一個異常說明,如果對所有成員和基類都說明了不會拋出異常变汪,則合成的成員是noexcept侠坎,如果合成成員調(diào)用的任意一個函數(shù)可能拋出異常,則合成的成員是noexcept(false)裙盾。如果我們定義了一個析構(gòu)函數(shù)实胸,但是沒有為它提供異常說明,編譯器將合成一個番官。
標準庫異常類
標準庫所生成的所有異常繼承自 std::exception庐完。
捕獲系統(tǒng)異常
C++標準只要求try catch捕獲throw出來的異常,并不要求捕獲系統(tǒng)異常(如被0除徘熔,段錯誤门躯,CPU異常等)。從C++層面來說酷师,不要期望try, catch能捕獲系統(tǒng)異常讶凉。不同操作系統(tǒng)有不同的方法捕捉系統(tǒng)異常。
errorFunc()捕捉不到系統(tǒng)異常山孔,代碼如下:
int errorFunc()
{
int p = 0;
try{
return *(int*)(p);
}
catch (...){
std::cout << "catch error" << std::endl;
}
}
int main()
{
errorFunc();
system("pause");
return 0;
}
Windows下可以使用SEH捕捉系統(tǒng)異常懂讯,Windows下try catch也是基于SEH實現(xiàn)的。
int errorFunc()
{
int p = 0;
__try {
return *(int*)(p);
}
__except(EXCEPTION_EXECUTE_HANDLER){
std::cout << "catch error" << std::endl;
}
}
int main()
{
errorFunc();
system("pause");
return 0;
}