C++ 程序拋出異常后執(zhí)行順序說(shuō)明
1 析構(gòu)函數(shù)中是否可以拋出異常
首先我們看一個(gè)常見(jiàn)的問(wèn)題,析構(gòu)函數(shù)中是否可以拋出異常。答案是C++標(biāo)準(zhǔn)指明析構(gòu)函數(shù)不能、也不應(yīng)該拋出異常!
C++異常處理模型是為C++語(yǔ)言量身設(shè)計(jì)的,更進(jìn)一步的說(shuō),它實(shí)際上也是為C++語(yǔ)言中面向?qū)ο蠖?wù)的。
C++異常處理模型最大的特點(diǎn)和優(yōu)勢(shì)就是對(duì)C++中的面向?qū)ο筇峁┝俗顝?qiáng)大的無(wú)縫支持。
那么如果對(duì)象在運(yùn)行期間出現(xiàn)了異常,C++異常處理模型有責(zé)任清除那些由于出現(xiàn)異常所導(dǎo)致的已經(jīng)失效了的對(duì)象(也即對(duì)象超出了它原來(lái)的作用域),并釋放對(duì)象原來(lái)所分配的資源, 這就是調(diào)用這些對(duì)象的析構(gòu)函數(shù)來(lái)完成釋放資源的任務(wù),所以從這個(gè)意義上說(shuō),析構(gòu)函數(shù)已經(jīng)變成了異常處理的一部分。
下面我們來(lái)看看析構(gòu)函數(shù)中不能拋出異常的兩個(gè)理由:
1)如果析構(gòu)函數(shù)拋出異常,則異常點(diǎn)之后的程序不會(huì)執(zhí)行,如果析構(gòu)函數(shù)在異常點(diǎn)之后執(zhí)行了某些必要的動(dòng)作比如釋放某些資源,則這些動(dòng)作不會(huì)執(zhí)行,會(huì)造成諸如資源泄漏的問(wèn)題。
2)通常異常發(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)題。
那么當(dāng)無(wú)法保證在析構(gòu)函數(shù)中不發(fā)生異常時(shí), 該怎么辦?
其實(shí)還是有很好辦法來(lái)解決的。那就是把異常完全封裝在析構(gòu)函數(shù)內(nèi)部,決不讓異常拋出函數(shù)之外。這是一種非常簡(jiǎn)單,也非常有效的方法。
//析構(gòu)函數(shù)
~Class()
{
try{
}
catch(){ //這里可以什么都不做,只是保證catch塊的程序拋出的異常不會(huì)被扔出析構(gòu)函數(shù)之外。
}
}
2 程序拋出異常后會(huì)怎樣
下面我們通過(guò)一個(gè)程序來(lái)觀察當(dāng)程序中拋出異常了是否會(huì)調(diào)用析構(gòu)函數(shù),異常拋出中throw()后面的語(yǔ)句是否還會(huì)執(zhí)行。
程序如下,我們創(chuàng)建一個(gè)類(lèi),然后構(gòu)造一個(gè)類(lèi)對(duì)象,當(dāng)拋出異常我們看程序是否會(huì)進(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; //類(lèi)方法
}
};
int main(void)
{
setTry newOne;
try{
throw("error!"); //直接拋出異常
newOne.dosomething();
}
catch (char* one){ //接收char*類(lèi)異常
cout << one << endl; // 2
}
catch (...){ //接收其他類(lèi)型異常
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()后面程序不會(huì)再執(zhí)行,而try外部后面的程序會(huì)繼續(xù)執(zhí)行。另外,析構(gòu)函數(shù)在生存期結(jié)束也會(huì)被調(diào)用。
補(bǔ)充:C++異常捕獲和處理
0. 寫(xiě)在前面
異常,讓一個(gè)函數(shù)可以在發(fā)現(xiàn)自己無(wú)法處理的錯(cuò)誤時(shí)拋出一個(gè)異常,希望它的調(diào)用者可以直接或者間接處理這個(gè)問(wèn)題。而傳統(tǒng)錯(cuò)誤處理技術(shù),檢查到一個(gè)錯(cuò)誤,返回退出碼或者終止程序等等,此時(shí)我們只知道有錯(cuò)誤,但不能更清楚的知道哪種錯(cuò)誤,因此,使用異常,就把錯(cuò)誤和處理分開(kāi)來(lái),由庫(kù)函數(shù)拋出異常,由調(diào)用者捕獲這個(gè)異常,調(diào)用者就可以知道程序函數(shù)庫(kù)調(diào)用出現(xiàn)錯(cuò)誤了,并去處理,而是否終止程序就把握在調(diào)用者手里了。
1. 異常的拋出和處理
1. 異常處理的語(yǔ)句
try區(qū)段:這個(gè)區(qū)段中包含了可能發(fā)生異常的代碼,在發(fā)生了異常之后,需要通過(guò)throw拋出。
throw子句:throw 子句用于拋出異常,被拋出的異??梢允荂++的內(nèi)置類(lèi)型(例如: throw int(1);),也可以是自定義類(lèi)型。
catch子句:每個(gè)catch子句都代表著一種異常的處理。catch子句用于處理特定類(lèi)型的異常。
例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)行開(kāi)空間時(shí),申請(qǐng)內(nèi)存失敗,就會(huì)拋出異常,此時(shí)捕獲到異常時(shí),就可告訴使用者是哪里的錯(cuò)誤,便于修改

2. 異常的處理規(guī)則
異常是通過(guò)拋出對(duì)象而引發(fā)的,該對(duì)象的類(lèi)型決定了應(yīng)該激活哪個(gè)處理代碼。
被選中的處理代碼是調(diào)用鏈中與該對(duì)象類(lèi)型匹配且離拋出異常位置最近的那一個(gè)。
拋出異常后會(huì)釋放局部存儲(chǔ)對(duì)象,所以被拋出的對(duì)象也就還給系統(tǒng)了,throw表達(dá)式會(huì)初始化一個(gè)拋出特殊的匿名對(duì)象,異常對(duì)象由編譯管理,異常對(duì)象在傳給對(duì)應(yīng)的catch處理之后撤銷(xiāo)。
例2:
class Exception//異常類(lèi)
{
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è)作用域,拋出的異常對(duì)象就銷(xiāo)毀了,這時(shí)會(huì)生成一個(gè)匿名對(duì)象先接受這個(gè)對(duì)象,并傳到外層棧幀。
throw e;
}
return a[pos];
}
protected:
int a[N];
};
int f()
{
try
{
Array<> a;
a[11];
}
catch (exception& e)
{
cout << e.what() << endl; //類(lèi)型不匹配,找離拋出異常位置最近且類(lèi)型匹配的那個(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)庫(kù)里面的異常,但拋出異常的對(duì)象是自己定義的異常類(lèi),故類(lèi)型不匹配,找離拋出異常最近的且類(lèi)型匹配的Exception

3. 異常處理?xiàng)U归_(kāi)
1.在try的語(yǔ)句塊內(nèi)聲明的變量在外部是不可以訪問(wèn)的,即使是在catch子句內(nèi)也不可以訪問(wèn)?! ?/p>
2.棧展開(kāi)(尋找異常處理(exception handling)代碼)
棧展開(kāi)會(huì)沿著嵌套函數(shù)的調(diào)用鏈不斷查找,知道找到了已拋出的異常匹配的catch子句。如果在最后還是沒(méi)有找到對(duì)應(yīng)的catch子句的話,則退出主函數(shù)后查找過(guò)程終止,程序調(diào)用標(biāo)準(zhǔn)函數(shù)庫(kù)的terminate()函數(shù),終止該程序的執(zhí)行
具體過(guò)程:
當(dāng)一個(gè)exception被拋出的時(shí)候,控制權(quán)會(huì)從函數(shù)調(diào)用中釋放出來(lái),并需找一個(gè)可以處理的catch子句
對(duì)于一個(gè)拋出異常的try區(qū)段,程序會(huì)先檢查與該try區(qū)段關(guān)聯(lián)的catch子句,如果找到了匹配的catch子句,就使用這個(gè)catch子句處理這個(gè)異常。
沒(méi)有找到匹配的catch子句,如果這個(gè)try區(qū)段嵌套在其他try區(qū)段中,則繼續(xù)檢查與外層try匹配的catch子句。如果仍然沒(méi)有找到匹配的catch子句,則退出當(dāng)前這個(gè)主調(diào)函數(shù),并在調(diào)用了剛剛退出的這個(gè)函數(shù)的其他函數(shù)中尋找。
3. catch子句的查找:
catch子句是按照出現(xiàn)的順序進(jìn)行匹配的(以例2來(lái)說(shuō),異常先會(huì)匹配catch(exception e)子句,然后在匹配 catch (Exception e)子句,一步一步的棧展開(kāi))。在尋找catch子句的過(guò)程中,拋出的異??梢赃M(jìn)行類(lèi)型轉(zhuǎn)換,但是比較嚴(yán)格:
允許從非常量轉(zhuǎn)換到常量的類(lèi)型轉(zhuǎn)換(權(quán)限縮?。?/p>
允許從派生類(lèi)到基類(lèi)的轉(zhuǎn)換。
允許數(shù)組被轉(zhuǎn)換成為指向數(shù)組(元素)類(lèi)型的指針,函數(shù)被轉(zhuǎn)換成指向該函數(shù)類(lèi)型的指針(降級(jí)問(wèn)題)
標(biāo)準(zhǔn)算術(shù)類(lèi)型的轉(zhuǎn)換(比如:把bool型和char型轉(zhuǎn)換成int型)和類(lèi)類(lèi)型轉(zhuǎn)換(使用類(lèi)的類(lèi)型轉(zhuǎn)換運(yùn)算符和轉(zhuǎn)換構(gòu)造函數(shù))。
4. 異常處理中需要注意的問(wèn)題
如果拋出的異常一直沒(méi)有函數(shù)捕獲(catch),則會(huì)一直上傳到c++運(yùn)行系統(tǒng)那里,導(dǎo)致整個(gè)程序的終止
一般在異常拋出后資源可以正常被釋放,但注意如果在類(lèi)的構(gòu)造函數(shù)中拋出異常,系統(tǒng)是不會(huì)調(diào)用它的析構(gòu)函數(shù)的,處理方法是:如果在構(gòu)造函數(shù)中要拋出異常,則在拋出前要記得刪除申請(qǐng)的資源。
異常處理僅僅通過(guò)類(lèi)型而不是通過(guò)值來(lái)匹配的,所以catch塊的參數(shù)可以沒(méi)有參數(shù)名稱,只需要參數(shù)類(lèi)型。
函數(shù)原型中的異常說(shuō)明要與實(shí)現(xiàn)中的異常說(shuō)明一致,否則容易引起異常沖突。
應(yīng)該在throw語(yǔ)句后寫(xiě)上異常對(duì)象時(shí),throw先通過(guò)Copy構(gòu)造函數(shù)構(gòu)造一個(gè)新對(duì)象,再把該新對(duì)象傳遞給 catch.
注:那么當(dāng)異常拋出后新對(duì)象如何釋放?
異常處理機(jī)制保證:異常拋出的新對(duì)象并非創(chuàng)建在函數(shù)棧上,而是創(chuàng)建在專用的異常棧上,因此它才可以跨接多個(gè)函數(shù)而傳遞到上層,否則在棧清空的過(guò)程中就會(huì)被銷(xiāo)毀。所有從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ì)被正確地銷(xiāo)毀。
catch塊的參數(shù)推薦采用地址傳遞而不是值傳遞,不僅可以提高效率,還可以利用對(duì)象的多態(tài)性。另外,派生類(lèi)的異常撲獲要放到父類(lèi)異常撲獲的前面,否則,派生類(lèi)的異常無(wú)法被撲獲。
編寫(xiě)異常說(shuō)明時(shí),要確保派生類(lèi)成員函數(shù)的異常說(shuō)明和基類(lèi)成員函數(shù)的異常說(shuō)明一致,即派生類(lèi)改寫(xiě)的虛函數(shù)的異常說(shuō)明至少要和對(duì)應(yīng)的基類(lèi)虛函數(shù)的異常說(shuō)明相同,甚至更加嚴(yán)格,更特殊。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
C語(yǔ)言算法練習(xí)之?dāng)?shù)組元素排序
這篇文章主要為大家介紹了C語(yǔ)言算法練習(xí)中數(shù)組元素排序的實(shí)現(xiàn)方法,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C語(yǔ)言有一定幫助,需要的可以參考一下2022-09-09
C++ OpenCV實(shí)現(xiàn)圖像去水印功能
本文將介紹如何使用OpenCV C++ 進(jìn)行簡(jiǎn)單圖像水印去除。我們?cè)诰W(wǎng)上download圖片時(shí),經(jīng)常因?yàn)榘鏅?quán)問(wèn)題有水印。本案例通過(guò)編寫(xiě)算法進(jìn)行簡(jiǎn)單水印去除。需要的可以參考一下2022-01-01
C++利用GPAC實(shí)現(xiàn)生成MP4文件的示例代碼
GPAC主要針對(duì)學(xué)生和內(nèi)容創(chuàng)作者,代表了一個(gè)跨平臺(tái)的多媒體框架,開(kāi)發(fā)人員可以使用它在?LGPL?許可下制作開(kāi)源媒體。本文就來(lái)用GPAC實(shí)現(xiàn)生成MP4文件,感興趣的可以了解一下2023-02-02
C++中二進(jìn)制數(shù)據(jù)序列化和反序列化詳解
這篇文章主要為大家詳細(xì)介紹了C++中二進(jìn)制數(shù)據(jù)序列化和反序列化的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的小伙伴可以了解下2023-11-11
用C語(yǔ)言實(shí)現(xiàn)從文本文件中讀取數(shù)據(jù)后進(jìn)行排序的功能
這是一個(gè)十分可靠的程序,這個(gè)程序的查錯(cuò)能力非常強(qiáng)悍。程序包含了文件操作,歸并排序和字符串輸入等多種技術(shù)。對(duì)大家學(xué)習(xí)C語(yǔ)言很有幫助,有需要的一起來(lái)看看。2016-08-08
visual studio 2015下boost庫(kù)配置教程
這篇文章主要為大家詳細(xì)介紹了visual studio 2015下boost庫(kù)的配置教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04
Qt為exe添加ico圖片的簡(jiǎn)單實(shí)現(xiàn)步驟
這篇文章主要給大家介紹了關(guān)于Qt為exe添加ico圖片的簡(jiǎn)單實(shí)現(xiàn)步驟,通過(guò)文中介紹的方法可以幫助大家實(shí)現(xiàn)這個(gè)自定義exe圖標(biāo)的效果,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07
C++中如何實(shí)現(xiàn)回調(diào)的方法示例
這篇文章主要給大家介紹了關(guān)于C++中如何實(shí)現(xiàn)回調(diào)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用c++具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-10-10

