C++ 程序拋出異常后執(zhí)行順序說明
1 析構(gòu)函數(shù)中是否可以拋出異常
首先我們看一個(gè)常見的問題,析構(gòu)函數(shù)中是否可以拋出異常。答案是C++標(biāo)準(zhǔn)指明析構(gòu)函數(shù)不能、也不應(yīng)該拋出異常!
C++異常處理模型是為C++語言量身設(shè)計(jì)的,更進(jìn)一步的說,它實(shí)際上也是為C++語言中面向?qū)ο蠖?wù)的。
C++異常處理模型最大的特點(diǎn)和優(yōu)勢就是對C++中的面向?qū)ο筇峁┝俗顝?qiáng)大的無縫支持。
那么如果對象在運(yùn)行期間出現(xiàn)了異常,C++異常處理模型有責(zé)任清除那些由于出現(xiàn)異常所導(dǎo)致的已經(jīng)失效了的對象(也即對象超出了它原來的作用域),并釋放對象原來所分配的資源, 這就是調(diào)用這些對象的析構(gòu)函數(shù)來完成釋放資源的任務(wù),所以從這個(gè)意義上說,析構(gòu)函數(shù)已經(jīng)變成了異常處理的一部分。
下面我們來看看析構(gòu)函數(shù)中不能拋出異常的兩個(gè)理由:
1)如果析構(gòu)函數(shù)拋出異常,則異常點(diǎn)之后的程序不會執(zhí)行,如果析構(gòu)函數(shù)在異常點(diǎn)之后執(zhí)行了某些必要的動(dòng)作比如釋放某些資源,則這些動(dòng)作不會執(zhí)行,會造成諸如資源泄漏的問題。
2)通常異常發(fā)生時(shí),c++的機(jī)制會調(diào)用已經(jīng)構(gòu)造對象的析構(gòu)函數(shù)來釋放資源,此時(shí)若析構(gòu)函數(shù)本身也拋出異常,則前一個(gè)異常尚未處理,又有新的異常,會造成程序崩潰的問題。
那么當(dāng)無法保證在析構(gòu)函數(shù)中不發(fā)生異常時(shí), 該怎么辦?
其實(shí)還是有很好辦法來解決的。那就是把異常完全封裝在析構(gòu)函數(shù)內(nèi)部,決不讓異常拋出函數(shù)之外。這是一種非常簡單,也非常有效的方法。
//析構(gòu)函數(shù) ~Class() { try{ } catch(){ //這里可以什么都不做,只是保證catch塊的程序拋出的異常不會被扔出析構(gòu)函數(shù)之外。 } }
2 程序拋出異常后會怎樣
下面我們通過一個(gè)程序來觀察當(dāng)程序中拋出異常了是否會調(diào)用析構(gòu)函數(shù),異常拋出中throw()后面的語句是否還會執(zhí)行。
程序如下,我們創(chuàng)建一個(gè)類,然后構(gòu)造一個(gè)類對象,當(dāng)拋出異常我們看程序是否會進(jìn)入析構(gòu)函數(shù)以及throw()拋出異常后面的程序:
#include<iostream> using namespace std; class setTry{ public: setTry(){ //構(gòu)造函數(shù) cout << "start!" << endl; // 1 } ~setTry(){ //析構(gòu)函數(shù) cout << "end!" << endl; // 4 } void dosomething(){ cout << "do something!" << endl; //類方法 } }; int main(void) { setTry newOne; try{ throw("error!"); //直接拋出異常 newOne.dosomething(); } catch (char* one){ //接收char*類異常 cout << one << endl; // 2 } catch (...){ //接收其他類型異常 cout << "..." << endl; } cout << "return 0!"<<endl; // 3 return 0; }
上面程序運(yùn)行結(jié)就是按標(biāo)注的1、2、3、4步驟輸出的,結(jié)果如下圖所示:
從運(yùn)行結(jié)果就可以看出,拋出異常try內(nèi)部的throw()后面程序不會再執(zhí)行,而try外部后面的程序會繼續(xù)執(zhí)行。另外,析構(gòu)函數(shù)在生存期結(jié)束也會被調(diào)用。
補(bǔ)充:C++異常捕獲和處理
0. 寫在前面
異常,讓一個(gè)函數(shù)可以在發(fā)現(xiàn)自己無法處理的錯(cuò)誤時(shí)拋出一個(gè)異常,希望它的調(diào)用者可以直接或者間接處理這個(gè)問題。而傳統(tǒng)錯(cuò)誤處理技術(shù),檢查到一個(gè)錯(cuò)誤,返回退出碼或者終止程序等等,此時(shí)我們只知道有錯(cuò)誤,但不能更清楚的知道哪種錯(cuò)誤,因此,使用異常,就把錯(cuò)誤和處理分開來,由庫函數(shù)拋出異常,由調(diào)用者捕獲這個(gè)異常,調(diào)用者就可以知道程序函數(shù)庫調(diào)用出現(xiàn)錯(cuò)誤了,并去處理,而是否終止程序就把握在調(diào)用者手里了。
1. 異常的拋出和處理
1. 異常處理的語句
try區(qū)段:這個(gè)區(qū)段中包含了可能發(fā)生異常的代碼,在發(fā)生了異常之后,需要通過throw拋出。
throw子句:throw 子句用于拋出異常,被拋出的異常可以是C++的內(nèi)置類型(例如: throw int(1);),也可以是自定義類型。
catch子句:每個(gè)catch子句都代表著一種異常的處理。catch子句用于處理特定類型的異常。
例2:
#include <iostream> using namespace std; void Test1() { try { char* p = new char[0x7fffffff]; //拋出異常 } catch (exception e) { cout << e.what() << endl; //捕獲異常,然后程序結(jié)束 } } int main() { Test1(); system("pause"); return 0; }
結(jié)果:
當(dāng)使用new進(jìn)行開空間時(shí),申請內(nèi)存失敗,就會拋出異常,此時(shí)捕獲到異常時(shí),就可告訴使用者是哪里的錯(cuò)誤,便于修改
2. 異常的處理規(guī)則
異常是通過拋出對象而引發(fā)的,該對象的類型決定了應(yīng)該激活哪個(gè)處理代碼。
被選中的處理代碼是調(diào)用鏈中與該對象類型匹配且離拋出異常位置最近的那一個(gè)。
拋出異常后會釋放局部存儲對象,所以被拋出的對象也就還給系統(tǒng)了,throw表達(dá)式會初始化一個(gè)拋出特殊的匿名對象,異常對象由編譯管理,異常對象在傳給對應(yīng)的catch處理之后撤銷。
例2:
class Exception//異常類 { public: Exception(const string& msg, int id) { _msg = msg; _id = id; } const char* What() const { return _msg.c_str(); } protected: string _msg; int _id; }; template<size_t N = 10> class Array { public: int& operator[](size_t pos) { if (pos >= N) { Exception e("下標(biāo)不合法", 1); //出了這個(gè)作用域,拋出的異常對象就銷毀了,這時(shí)會生成一個(gè)匿名對象先接受這個(gè)對象,并傳到外層棧幀。 throw e; } return a[pos]; } protected: int a[N]; }; int f() { try { Array<> a; a[11]; } catch (exception& e) { cout << e.what() << endl; //類型不匹配,找離拋出異常位置最近且類型匹配的那個(gè)。 } return 0; } int main() { try { f(); } catch (Exception& e) { cout << e.What() << endl; } system("pause"); return 0; }
結(jié)果:
f()函數(shù)中捕獲的異常是標(biāo)準(zhǔn)庫里面的異常,但拋出異常的對象是自己定義的異常類,故類型不匹配,找離拋出異常最近的且類型匹配的Exception
3. 異常處理?xiàng)U归_
1.在try的語句塊內(nèi)聲明的變量在外部是不可以訪問的,即使是在catch子句內(nèi)也不可以訪問?! ?/p>
2.棧展開(尋找異常處理(exception handling)代碼)
棧展開會沿著嵌套函數(shù)的調(diào)用鏈不斷查找,知道找到了已拋出的異常匹配的catch子句。如果在最后還是沒有找到對應(yīng)的catch子句的話,則退出主函數(shù)后查找過程終止,程序調(diào)用標(biāo)準(zhǔn)函數(shù)庫的terminate()函數(shù),終止該程序的執(zhí)行
具體過程:
當(dāng)一個(gè)exception被拋出的時(shí)候,控制權(quán)會從函數(shù)調(diào)用中釋放出來,并需找一個(gè)可以處理的catch子句
對于一個(gè)拋出異常的try區(qū)段,程序會先檢查與該try區(qū)段關(guān)聯(lián)的catch子句,如果找到了匹配的catch子句,就使用這個(gè)catch子句處理這個(gè)異常。
沒有找到匹配的catch子句,如果這個(gè)try區(qū)段嵌套在其他try區(qū)段中,則繼續(xù)檢查與外層try匹配的catch子句。如果仍然沒有找到匹配的catch子句,則退出當(dāng)前這個(gè)主調(diào)函數(shù),并在調(diào)用了剛剛退出的這個(gè)函數(shù)的其他函數(shù)中尋找。
3. catch子句的查找:
catch子句是按照出現(xiàn)的順序進(jìn)行匹配的(以例2來說,異常先會匹配catch(exception e)子句,然后在匹配 catch (Exception e)子句,一步一步的棧展開)。在尋找catch子句的過程中,拋出的異常可以進(jìn)行類型轉(zhuǎn)換,但是比較嚴(yán)格:
允許從非常量轉(zhuǎn)換到常量的類型轉(zhuǎn)換(權(quán)限縮?。?/p>
允許從派生類到基類的轉(zhuǎn)換。
允許數(shù)組被轉(zhuǎn)換成為指向數(shù)組(元素)類型的指針,函數(shù)被轉(zhuǎn)換成指向該函數(shù)類型的指針(降級問題)
標(biāo)準(zhǔn)算術(shù)類型的轉(zhuǎn)換(比如:把bool型和char型轉(zhuǎn)換成int型)和類類型轉(zhuǎn)換(使用類的類型轉(zhuǎn)換運(yùn)算符和轉(zhuǎn)換構(gòu)造函數(shù))。
4. 異常處理中需要注意的問題
如果拋出的異常一直沒有函數(shù)捕獲(catch),則會一直上傳到c++運(yùn)行系統(tǒng)那里,導(dǎo)致整個(gè)程序的終止
一般在異常拋出后資源可以正常被釋放,但注意如果在類的構(gòu)造函數(shù)中拋出異常,系統(tǒng)是不會調(diào)用它的析構(gòu)函數(shù)的,處理方法是:如果在構(gòu)造函數(shù)中要拋出異常,則在拋出前要記得刪除申請的資源。
異常處理僅僅通過類型而不是通過值來匹配的,所以catch塊的參數(shù)可以沒有參數(shù)名稱,只需要參數(shù)類型。
函數(shù)原型中的異常說明要與實(shí)現(xiàn)中的異常說明一致,否則容易引起異常沖突。
應(yīng)該在throw語句后寫上異常對象時(shí),throw先通過Copy構(gòu)造函數(shù)構(gòu)造一個(gè)新對象,再把該新對象傳遞給 catch.
注:那么當(dāng)異常拋出后新對象如何釋放?
異常處理機(jī)制保證:異常拋出的新對象并非創(chuàng)建在函數(shù)棧上,而是創(chuàng)建在專用的異常棧上,因此它才可以跨接多個(gè)函數(shù)而傳遞到上層,否則在棧清空的過程中就會被銷毀。所有從try到throw語句之間構(gòu)造起來的對象的析構(gòu)函數(shù)將被自動(dòng)調(diào)用。但如果一直上溯到main函數(shù)后還沒有找到匹配的catch塊,那么系統(tǒng)調(diào)用terminate()終止整個(gè)程序,這種情況下不能保證所有局部對象會被正確地銷毀。
catch塊的參數(shù)推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對象的多態(tài)性。另外,派生類的異常撲獲要放到父類異常撲獲的前面,否則,派生類的異常無法被撲獲。
編寫異常說明時(shí),要確保派生類成員函數(shù)的異常說明和基類成員函數(shù)的異常說明一致,即派生類改寫的虛函數(shù)的異常說明至少要和對應(yīng)的基類虛函數(shù)的異常說明相同,甚至更加嚴(yán)格,更特殊。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
C/C++函數(shù)調(diào)用的幾種方式總結(jié)
本篇文章主要是對C/C++函數(shù)調(diào)用的幾種方式進(jìn)行了詳細(xì)的總結(jié)介紹,需要的朋友可以過來參考下,希望對大家有所幫助2013-12-12VS2022創(chuàng)建Windows服務(wù)程序的方法步驟
本文主要介紹了VS2022創(chuàng)建Windows服務(wù)程序的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05C語言編程數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)詳解小白篇
這篇文章主要介紹了數(shù)據(jù)結(jié)構(gòu)的基礎(chǔ),非常適合初學(xué)數(shù)據(jù)結(jié)構(gòu)的小白,有需要的朋友可以借鑒參考下,希望可以有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-09-09