當(dāng)前位置:首頁文章首頁 IT學(xué)院 IT技術(shù)

C++華麗的exception handling(異常處理)背后隱藏的陰暗面及其處理方法

作者:  來源:  發(fā)布時(shí)間:2012-2-1 8:32:23  點(diǎn)擊:

最近在看auto_ptr源碼的時(shí)候,發(fā)現(xiàn)里面的異常說明很多。事實(shí)上對于exception handling這塊,以前也有很多困惑的地方,只是由于平時(shí)代碼中很少用到,于是就從來沒仔細(xì)鉆研過。本來這篇是用來寫smart pointer的,既然遇到了exception handling這塊,那么先把這塊硬骨頭啃下來再說吧。
翻閱了很多大師的經(jīng)典著作,發(fā)現(xiàn)exception handling在《c++ primer》中只是概念性的提了下,對于技巧型的內(nèi)容幾乎沒有涉及到;《effective c++》中只有一個(gè)條款中提及,《inside c++ object model》也提到很少,幸運(yùn)的是《more effective c++》中卻有一個(gè)專題來研討這塊,而且講到很多技巧性的內(nèi)容,令人膾炙人口。。 雖然新公司里很少用到exception handling,所有的狀態(tài)信息都是以日志文件形式來記錄,而誰也不能保證以后的工作中不會用到,對于一般性的異常處理,自認(rèn)為還是可以應(yīng)付的來的,至少不會導(dǎo)致因?yàn)闆]有處理的exception而teminate了當(dāng)前程序,而如果要寫出高質(zhì)量高穩(wěn)定性的C++代碼,不掌握exception handling的技巧性使用應(yīng)該是很難的(至少我是這么認(rèn)為的),當(dāng)然了,C陣營中錯(cuò)誤代碼或返回狀態(tài)信息是另一類exception技能了。而正如Scott Meyers在《effective c++》的條款一所說:視C++為一個(gè)語言聯(lián)邦;因而不該因?yàn)镃++是由C發(fā)展而來而全盤忽視了C++的特性。。。
 
VS編譯器對異常規(guī)范的忽視
異常規(guī)范在《C++ Primer》中倒是提及的多一些。這一塊也是exception handling中最令人頭大的一塊,因?yàn)楸M管編譯器能檢測出少量異常規(guī)范,對于大多數(shù)的異常規(guī)范,只有在運(yùn)行期才能知曉,如果違反了異常規(guī)范,諸多大師的一致解釋是:程序會自動(dòng)調(diào)用標(biāo)準(zhǔn)庫的unexpacted函數(shù),此函數(shù)又調(diào)用teminate函數(shù),從而直接終止程序的運(yùn)行。來看看如下代碼:
#include <iostream>
 
using namespace std;
 
 
typedef void (*CallBackPtr)(int nEventLocationX,int nEventLocationY,void *pDataToPassValue)throw();
 
class CallBack
{
public:
    CallBack(CallBackPtr fPtr,void *pDataToPassValue):func(fPtr),pData(pDataToPassValue)
    {}
    void MakeCallBack(int nEventLocationX,int nEventLocationY) const throw()
    {
        func(nEventLocationX,nEventLocationY,pData);
    };
 
private:
    CallBackPtr func;
    void *pData;
};
 
void MyFunc(int nEventLocationX,int nEventLocationY,void *pDataToPassValue) throw(runtime_error)
{
    cout<<nEventLocationX<<" "<<nEventLocationY<<endl;
    throw runtime_error("runtime error example!");
}
 
int main(int *argc , char **argv)
{
    CallBackPtr Func = MyFunc;
    
    CallBack MyCallBack(Func,NULL);
    MyCallBack.MakeCallBack(10,10);
    return 0;
}

這是一個(gè)回調(diào)函數(shù)管理類的例子,是《more effective c++》的原例,如果按照Lippman在《C++ Primer》中所說的話,此程序應(yīng)該不能通過編譯,因?yàn)樗嬖谝粋(gè)編譯期就能檢測出來的違反異常規(guī)范的地方:對于函數(shù)MyFunc和函數(shù)定義CallBackPtr異常聲明,MyFunc的規(guī)范更為嚴(yán)格,它應(yīng)該不能轉(zhuǎn)化為CallBackPtr的對象才是,因?yàn)镃allBackPtr的異常規(guī)范為throw(),意味著此函數(shù)不會拋出任何異常。。而我在VS2008下卻能正常編譯通過,只有一個(gè)warning:C++ exception specification ignored except to indicate a function is not __declspec(nothrow),MSDN中對其解釋是:“使用異常規(guī)范聲明函數(shù),Visual C++ 接受但并不實(shí)現(xiàn)此規(guī)范。包含在編譯期間被忽略的異常規(guī)范的代碼可能需要重新編譯和鏈接,以便在支持異常規(guī)范的未來版本中重用。”意即VC編譯器不支持異常規(guī)格說明。。 令我牽腸掛肚的是:倘若一直如此的話,那么C++的異常規(guī)范特性得全部由程序員來掌控,感嘆VC編譯器對于這點(diǎn)多少有些不人道。。 但我也不想因此而以點(diǎn)蓋面的全盤否定VC編譯器在其它方面的高性能。
使用smart pointer來防止destructor中的資源泄露
對于由于異常處理不當(dāng)而引發(fā)的資源泄露,無疑亦是程序員喜歡討論的話題之一,因?yàn)閽伋霎惓R馕吨粋(gè)拋異常的代碼塊可能只執(zhí)行了一部分(前提是當(dāng)前函數(shù)沒有處理異常),這樣的話,那么異常又會傳送到當(dāng)前代碼塊的外圍去處理,而引發(fā)資源泄露的代碼塊往往卻是這塊沒被執(zhí)行的代碼,看看如下例子:
#include <iostream>
using namespace std;
 
class BaseClass
{
public:
    BaseClass(){};
    ~BaseClass(){};
};
 
void ExceptionFunc() throw(runtime_error)
{
    throw runtime_error("example exception handling!");
}
 
void Function() throw(runtime_error)
{
    BaseClass *pBase = new BaseClass;
 
    ExceptionFunc();
    
    delete pBase;
}
 
int main(int *argc , char **argv)
{
    try
    {
        Function();
    }
    catch(runtime_error &err)
    {
        cout<<err.what()<<endl;
    }
    return 0;
}
 
不得不承認(rèn)在main函數(shù)返回之前,所有的異常確實(shí)得到了處理,讓人難以忽視的是:Function里面拋出異常后,delete pBase沒有執(zhí)行,這就意味著發(fā)生了內(nèi)存泄露。。指針無處不在,如果我們不想用指針而想提高代碼的效率和質(zhì)量,那幾乎是不可能的。事實(shí)上,我們可以在Function函數(shù)里捕捉異常,然后在異常處理塊中執(zhí)行delete pBase,可以避免由此引發(fā)的內(nèi)存泄露,然而這樣做的缺陷是要寫兩個(gè)delete, Scott Meyers對于這種引發(fā)內(nèi)存的更好的處理方式是:使用smart pointer。 如果將Function改為如下:
void Function() throw(runtime_error)
{
    BaseClass *pBase = new BaseClass;
    auto_ptr<BaseClass> PtrBase(pBase);
    ExceptionFunc();
}
 
用類來管理資源是防止資源泄露的有力法器之一,這種情況下異常拋出后,auto_ptr對象肯定會執(zhí)行析構(gòu)函數(shù),此時(shí)會自動(dòng)釋放其指針成員指向的對象資源,即便它的對象為NULL,由于C++保證了delete空指針無異常的特性,所以資源是肯定會正確的釋放。然而在我看來,smart pointer的使用也只是一種折中而已,因?yàn)槭褂胊uto_ptr而帶來的負(fù)面性后果其實(shí)也可以大作討論了,有待我之后的smart pointer文章再作詳細(xì)討論。 
異常逃離destructor的災(zāi)難性后果
這一點(diǎn)也是唯一一條Sotte Meyyers在《effective c++》(條款8)和《more effective c++》(第五章節(jié))中重復(fù)討論了兩次的條款,當(dāng)destructor中無法處理異常的話,程序會直接調(diào)用teminate從而終止。。如果試圖在destructor外部捕獲異常,那將是徒勞的,正如一般重載delete運(yùn)算符的聲明式一樣,往往在后面又加個(gè)異常規(guī)范throw(),這意味著delete外部根本無法捕捉到其內(nèi)部的異常。

文章評論

軟件按字母排列: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z