C++异常处理机制详解
異常處理是一種允許兩個(gè)獨(dú)立開發(fā)的程序組件在程序執(zhí)行期間遇到程序不正常的情況(異常exception)時(shí)相互通信的機(jī)制。本文總結(jié)了19個(gè)C++異常處理中的常見問(wèn)題,基本涵蓋了一般C++程序開發(fā)所需的關(guān)于異常處理部分的細(xì)節(jié)。
?
1. throw可以拋出哪些種類的異常對(duì)象?如何捕獲?
1)異常對(duì)象通常是一個(gè)class對(duì)象, 通常用以下代碼拋出:
// 調(diào)用的類的構(gòu)造函數(shù)
throw popOnEmpty();
但是throw 表達(dá)式也可以拋出任何類型的對(duì)象, 例如(雖然很不常見)在下面的代碼例子中,函數(shù)mathFunc()拋出一個(gè)枚舉類型的異常對(duì)象
enum EHstate { noErr, zeroOp, negativeOp, severeError };
int mathFunc( int i )
{
??? if ( i == 0 )
??? throw zeroOp; // 枚舉類型的異常
}
2)拋出異常的語(yǔ)句或其調(diào)用函數(shù)要在try塊中才能被捕獲。
?
2. catch子句的語(yǔ)法
一個(gè)catch 子句由三部分構(gòu)成:
1)關(guān)鍵字catch
2)異常聲明,在括號(hào)中的單個(gè)類型或單個(gè)對(duì)象聲明被(稱作異常聲明,exception declaration)
3)復(fù)合語(yǔ)句中的一組語(yǔ)句。
// stackExcp.h
class popOnEmpty { };
class popOnFull { };
catch ( pushOnFull )
{
??? cerr << "trying to push a value on a full stack\n";
??? return errorCode88;
}
?
3. 異常聲明可以只是一個(gè)類型聲明而不是對(duì)象聲明嗎?
catch 子句的異常聲明可以是一個(gè)類型聲明或一個(gè)對(duì)象聲明。當(dāng)我們要獲得throw 表達(dá)式的值或者要操縱throw 表達(dá)式所創(chuàng)建的異常對(duì)象時(shí),我們應(yīng)該聲明一個(gè)對(duì)象。
catch ( pushOnFull eObj )
{
??? cerr << "trying to push the value " << eObj.value() << " on a full stack\n";
}
?
4. 異常聲明中異常對(duì)象的拷貝過(guò)程?
catch 子句異常聲明的行為特別像參數(shù)聲明。同理,也可以分出按值傳遞和引用傳遞(指針)。通常采用的是引用傳遞。
例1:按值傳遞。當(dāng)進(jìn)入catch 子句時(shí),如果異常聲明聲明了一個(gè)對(duì)象,則用該異常對(duì)象的拷貝初始化這個(gè)對(duì)象。例中對(duì)象eObj 是用異常對(duì)象的值來(lái)初始化的,會(huì)調(diào)用拷貝構(gòu)造函數(shù)。
void calculate( int op ) {
try {
mathFunc( op );
}
catch (pushOnFull eObj ) {
// eObj 是被拋出的異常對(duì)象的拷貝
}
}
例2:引用傳遞。catch子句可以直接引用由throw 表達(dá)式創(chuàng)建的異常對(duì)象,而不是創(chuàng)建一個(gè)局部拷貝。可以防止不必要地拷貝大型類對(duì)象。
void calculate( int op ) {
try {
mathFunc( op );
}
catch (pushOnFull &eObj ) {
// eObj 引用了被拋出的異常對(duì)象
}
}
?
5. 異常處理的棧展開過(guò)程是什么?
在查找用來(lái)處理被拋出異常的catch 子句時(shí),因?yàn)楫惓6顺鰪?fù)合語(yǔ)句和函數(shù)定義,這個(gè)過(guò)程被稱作棧展開(stack unwinding)。隨著棧的展開,在退出的復(fù)合語(yǔ)句和函數(shù)定義中聲明的局部變量的生命期也結(jié)束了。C++保證,隨著棧的展開,盡管局部類對(duì)象的生命期是因?yàn)閽伋霎惓6唤Y(jié)束,但是這些局部類對(duì)象的析構(gòu)函數(shù)也會(huì)被調(diào)用。
?
6. 異常拋出沒(méi)有在try塊中或拋出的異常沒(méi)有對(duì)應(yīng)的catch語(yǔ)句來(lái)捕捉,結(jié)果如何?
異常不能夠保持在未被處理的狀態(tài),異常對(duì)于一個(gè)程序非常重要,它表示程序不能夠繼續(xù)正常執(zhí)行。如果沒(méi)有找到處理代碼,程序就調(diào)用C++標(biāo)準(zhǔn)庫(kù)中定義的函數(shù)terminate()。terminate()的缺省行為是調(diào)用abort() ,指示從程序非正常退出。
?
7.為什么要重新拋出異常?怎么寫?
在異常處理過(guò)程中也可能存在“單個(gè)catch 子句不能完全處理異常”的情況。在對(duì)異常對(duì)象進(jìn)行修改或增加某些信息之后,catch 子句可能決定該異常必須由函數(shù)調(diào)用鏈中更上級(jí)的函數(shù)來(lái)處理。表達(dá)式的形式為:throw;
例子如下:
try
????? ??{
??????????? entryDescr->checkMandatoryData(beModel_);
??????? }
??????? catch (CatchableOAMexception & error) // 只能用引用聲明
??????? {
??????????? vector<string> paramList;
??????????? paramList.push_back(currentDn);
??????????? error.addFrameToEnd(6,paramList);? // 修改異常對(duì)象
??????????? throw;? //重新拋出異常, 并由另一個(gè)catch 子句來(lái)處理
??????? }
注意1:被重新拋出的異常就是原來(lái)的異常對(duì)象,所以異常聲明一定要用引用。
注意2:在catch 語(yǔ)句里也可以拋出其它
?
8. 怎么捕捉全部異常或未知異常?
可以用catch ( ... ) { } 。
作用在于:1. 可以釋放在前面獲得的資源(如動(dòng)態(tài)內(nèi)存),因?yàn)楫惓M顺?#xff0c;這些資源為釋放。2. 捕獲其余類型的未知異常。
catch 子句被檢查的順序與它們?cè)趖ry 塊之后出現(xiàn)的順序相同。一旦找到了一個(gè)匹配,則后續(xù)的catch 子句將不再檢查。這意味著如果catch(...)與其他catch 子句聯(lián)合使用,它必須總是被放在異常處理代碼表的最后,否則就會(huì)產(chǎn)生一個(gè)編譯時(shí)刻錯(cuò)誤。例子如下:
catch ( pushOnFull ) {}
catch ( popOnEmpty ) { }
catch (...) { } // 必須是最后一個(gè)catch 子句
?
9. 為什么 catch 子句的異常聲明通常被聲明為引用?
1)可以避免由異常對(duì)象到 catch 子句中的對(duì)象的拷貝,特別是對(duì)象比較大時(shí)。
2)能確保catch子句對(duì)異常對(duì)象的修改能再次拋出。
3)確保能正確地調(diào)用與異常類型相關(guān)聯(lián)的虛擬函數(shù),避免對(duì)象切割。
具體參見4,7,17。
?
10. 異常對(duì)象的生命周期?
產(chǎn)生:throw className()時(shí)產(chǎn)生。
銷毀:該異常的最后一個(gè)catch 子句退出時(shí)銷毀
注意:因?yàn)楫惓?赡茉赾atch子句中被重新拋出,所以在到達(dá)最后一個(gè)處理該異常的catch 子句之前,異常對(duì)象是不能被銷毀的。
?
11. const char *到char * 非法的異常類型轉(zhuǎn)換。
我們注意到下面的代碼在VC中可以正常運(yùn)行(gcc不能)。
try { throw "exception";}
catch (char *) {cout << "exception catch!" <<endl;}
實(shí)際上throw的是一個(gè)const char *, catch的時(shí)候轉(zhuǎn)型成char *。這是C++對(duì)C的向下兼容。
同樣的問(wèn)題存在于:
1. char *p =? “test”; // 也是一個(gè)const char * 到char *轉(zhuǎn)型。
2. void func(char* p) { printf("%s\n", p); }
??? func("abc"); // const char * 到char *
以上兩例在編譯時(shí)不警告,運(yùn)行時(shí)不出錯(cuò),是存在隱患的。
?
12. 異常規(guī)范(exception specification)的概念?
異常規(guī)范在函數(shù)聲明是規(guī)定了函數(shù)可以拋出且只能拋出哪些異常。空的異常規(guī)范保證函數(shù)不會(huì)拋出任何異常。如果一個(gè)函數(shù)聲明沒(méi)有指定異常規(guī)范,則該函數(shù)可以拋出任何類型的異常。
例1:函數(shù)Pop若有異常,只能拋出popOnEmpty和string類型的異常對(duì)象
void pop( int &value ) throw(popOnEmpty, string);
例2:函數(shù)no_problem()保證不會(huì)拋出任何異常
extern void no_problem() throw();
例3:函數(shù)problem()可以拋出任何類型的異常
extern void problem();
?
13. 函數(shù)指針的異常規(guī)范?
我們也可以在函數(shù)指針的聲明處給出一個(gè)異常規(guī)范。例如:
void (*pf) (int) throw(string);
當(dāng)帶有異常規(guī)范的函數(shù)指針被初始化或被賦值時(shí),用作初始值或右值的指針異常規(guī)范必須與被初始化或賦值的指針異常規(guī)范一樣或更嚴(yán)格。例如:
void recoup( int, int ) throw(exceptionType);
void no_problem() throw();
void doit( int, int ) throw(string, exceptionType);
// ok: recoup() 與 pf1 的異常規(guī)范一樣嚴(yán)格
void (*pf1)( int, int ) throw(exceptionType) = &recoup;
// ok: no_problem() 比 pf2 更嚴(yán)格
void (*pf2)() throw(string) = &no_problem;
// 錯(cuò)誤: doit()沒(méi)有 pf3 嚴(yán)格
void (*pf3)( int, int ) throw(string) = &doit;
注:在VC和gcc上測(cè)試失敗。
?
14. 派生類中虛函數(shù)的異常規(guī)范的聲明?
基類中虛擬函數(shù)的異常規(guī)范,可以與派生類改寫的成員函數(shù)的異常規(guī)范不同。但是派生類虛擬函數(shù)的異常規(guī)范必須與基類虛擬函數(shù)的異常規(guī)范一樣或者更嚴(yán)格。
class Base {
public:
virtual double f1( double ) throw ();
virtual int f2( int ) throw ( int );
virtual string f3( ) throw ( int, string );
// ...
};
class Derived : public Base {
public:
// error: 異常規(guī)范沒(méi)有 base::f1() 的嚴(yán)格
double f1( double ) throw ( string );
// ok: 與 base::f2() 相同的異常規(guī)范
int f2( int ) throw ( int );
// ok: 派生 f3() 更嚴(yán)格
string f3( ) throw ( int );
// ...
};
?
15. 被拋出的異常的類型和異常規(guī)范中指定的類型能進(jìn)行類型轉(zhuǎn)換嗎?
int convert( int parm ) throw(string)
{
if ( somethingRather )
// 程序錯(cuò)誤:
// convert() 不允許 const char* 型的異常
throw "help!";
}
throw 表達(dá)式拋出一個(gè)C 風(fēng)格的字符串,由這個(gè)throw 表達(dá)式創(chuàng)建的異常對(duì)象的類型為const char*。通常,const char*型的表達(dá)式可以被轉(zhuǎn)換成string 類型。但是,異常規(guī)范不允許從被拋出的異常類型到異常規(guī)范指定的類型之問(wèn)的轉(zhuǎn)換。
注意:
當(dāng)異常規(guī)范指定一個(gè)類類型(類類型的指針)時(shí),如果一個(gè)異常規(guī)范指定了一個(gè)類,則該函數(shù)可以拋出“從該類公有派生的類類型”的異常對(duì)象。類指針同理。
例如:
class popOnEmpty : public stackExcp { };
void stackManip() throw( stackExcp )? // 異常規(guī)范是stackExcp類型
{
??? throw stackExcp();??????????? // 與異常規(guī)范一樣
??? throw popOnEmpty ();????? // ok. 是stackExcp的派生類
}
?
16. 公有基類的catch子句可以捕捉到其派生類的異常對(duì)象。
int main( ) {
try {
// 拋出pushOnFull異常
}
catch ( Excp ) {
// 處理 popOnEmpty 和 pushOnFull 異常
throw;
}
catch ( pushOnFull ) {
// 處理 pushOnFull 異常
}
}
在上例中,進(jìn)入catch ( Excp )子句,重新拋出的異常任然是pushOnFull類型的異常對(duì)象,而不會(huì)是其基類對(duì)象Excp。
?
17. 異常對(duì)象中怎么運(yùn)用虛擬函數(shù)來(lái)完成多態(tài)?
1)異常申明是對(duì)象(不是引用或指針),類似于普通的函數(shù)調(diào)用,發(fā)生對(duì)象切割。
// 定義了虛擬函數(shù)的新類定義
class Excp {
public:
virtual void print() {
cerr << "An exception has occurred"
<< endl;
}
};
class stackExcp : public Excp { };
class pushOnFull : public stackExcp {
public:
virtual void print() {
cerr << "trying to push the value " << _value
<< " on a full stack\n";
}
// ...
};
int main( ) {
try {
// iStack::push() throws a pushOnFull exception
} catch ( Excp eObj ) {
eobj.print(); // 調(diào)用虛擬函數(shù)
// 喔! 調(diào)用基類實(shí)例
}
}
對(duì)象切割過(guò)程:eObj 以“異常對(duì)象的基類子對(duì)象Excp 的一個(gè)拷貝”作為初始值,eobj 是Excp 類型的對(duì)象,而不是pushOnFull 類型的對(duì)象。
輸出結(jié)果:
An exception has occurred
2)異常聲明是一個(gè)指針或引用
int main( ) {
try {
// iStack::push() 拋出一個(gè) pushOnFull 異常
}
catch ( Excp &eObj ) {
eobj.print(); // 調(diào)用虛擬函數(shù) pushOnFull::print()
}
}
輸出結(jié)果:
trying to push the value 879 on a full stack
?
18. function try block(函數(shù)try塊)
把整個(gè)函數(shù)體包含在一個(gè)try塊中
int main()
try {
// main() 的函數(shù)體
}
catch ( pushOnFull ) {
// ...
}
catch ( popOnEmpty ) {
// ...
}
?
19. 為什么類的構(gòu)造函數(shù)需要函數(shù)try塊?
如下例,普通的try塊
inline Account::無(wú)法處理成員初始化表中的異常,若serviceCharge拋出異常,則這個(gè)異常無(wú)法被捕捉到。
Account( const char* name, double opening_bal )
: _balance( opening_bal - serviceCharge() )
{
try {
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
catch ( ...) {
// 特殊處理
// 不能捕獲來(lái)自成員初始化表的異常
}
}
改進(jìn)后如下,使用函數(shù)try 塊是保證“在構(gòu)造函數(shù)中捕獲所有在對(duì)象構(gòu)造期間拋出的異常”的惟一解決方案。關(guān)鍵字try 應(yīng)該被放在成員初始化表之前,try 塊的復(fù)合語(yǔ)句包圍了構(gòu)造函數(shù)體。
inline Account::
Account( const char* name, double opening_bal )
try
: _balance( opening_bal - serviceCharge() )
{
_name = new char[ strlen(name)+1 ];
strcpy( _name, name );
_acct_nmbr = get_unique_acct_nmbr();
}
catch ( ... )
{
// 特殊處理
// 現(xiàn)在能夠捕獲來(lái)自 ServiceCharge() 的異常了
}
?
參考文獻(xiàn):
C++ Primer第三版
?
總結(jié)
以上是生活随笔為你收集整理的C++异常处理机制详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: c++异常处理机制示例及讲解
- 下一篇: C/C++中的运算符优先级总结