結構異常和C++異常

注: 本文原出處不詳!

Windows結構異常有如下幾個特性:
1、它使用__try、__except、__finally和__leave關鍵字和RaiseException API;
2、它由Windows所支持,因此它不適合其它操作系統
3、它不處理C++對象的解析
說明:在使用Windows結構異常的函數內,如果有C++對象,編譯器會發
出:error C2712: Cannot use __try in functions that require object unwinding 的錯誤。
如:
void fun( )
{
        CObject       object ; // 錯誤!結構化異常無法解析C++ 對象
__try{
...

              __except(...){
                     ...
              }
       }
4、它用爲硬件異常(例如訪問非法或被零除)或操作系統異常的結果被拋出。
       也可以作爲RaiseException函數的結果被拋出;
 
而C++異常的特點是:
1、使用try、throw和catch等關鍵字;
2、它處理C++對象的解析;
3、它作爲throw語句的結果被拋出;
 
注:MFC提供了第三種異常處理機制。它使用幾個異常處理宏,這些宏現在也被編譯成C++異常,因此沒有必要在新的代碼中使用它們!在MFC編程中,異
常對象都是從CExcept派生。大多數MFC異常處理對象都是動態分配的,因此當它們被捕獲時,必須被刪除;而沒有捕獲的MFC異常由MFC本身在AfxCa-
llWndProc函數中捕獲並刪除。
因爲C++異常不能處理硬件和操作系統異常,因此需要將結構異常轉化爲
C++異常。
 
如何將結構異常轉化爲C++異常
Visual C++ 允許你通過使用_set_se_translator函數將結構異常轉化爲C++異常。
 
#i nclude <eh.h>
class CSEHException
{
       public :
       CSEHException( UINT code , PEXCEPTION_POINTERS pep )
{
       m_exceptionCode             = code ;
       m_exceptionRecord    = *pep->ExceptionRecord ;
       m_context                  = *pep->ContextRecord ;
       ASSERTE( m_exceptionCode == m_exceptionRecord.ExceptionCode );
}
operator unsigned int() { return m_exceptionCode ; }
 
UINT m_exceptionCode ;
EXCEPTION_RECORD   m_exceptionRecord ;
CONTEXT   m_context ;
} ;
 
// 結構化異常到C++異常轉化器
void cdecl TranslateSEHtoCE( UINT code , PEXCEPTIONPOINTERS pep )
{
throw CSEHException( code , pep ) ;
}
 
int APIENTRY WinMain( ……)
{
// 安裝異常轉化器
setse_translator( TranslateSEHtoCE ) ;
}
 
注意,異常轉化器在每個線程的基礎上進行工作,因此你需要爲每一個線程安裝一個轉化器。這種異常轉化器有一個副作用,你會在輸出窗口的Debug標籤中看見兩個異常跟蹤消息:一個是爲了原來的結構異常,另一個是爲了轉化後的C++異常。使用了異常轉化器後,你就可以像捕獲C++異常一樣捕獲結構異常。
 
異常相關的編譯選項
先看下面的函數:
void TestBugException()
{
try{
int *pInt = 0 ;
*pInt = 42 ;
}
catch(…){
MessageBox(0,””,0,MB_OK);}}
顯然,上面的代碼將會產生一個結構化異常,而處理異常卻是用C++的異常
機制,這個異常會不會被處理呢?答案是決定於編譯環境,在VC的默認情況
下,在調試版本中處理異常,而在發佈版中並不進行處理。對於VC而言,
C++異常只能由throw語句拋出,或者在調用的函數內有throw語句。任何其它
的代碼都不能接收C++異常,因此調式器將認爲在try語句內沒有C++異常(
因爲沒有throw語句),因而在發佈版裏將不需要的異常處理代碼除去,以使程序得到優化。
       上面描述的異常被稱爲同步異常模型,由/GX編譯選項設置,而且是VC6的默認設置。與之相對的是異步異常模型,其採取任何指令都會產生
異常的機制,異步異常模型由/EHA編譯選項設置。即/EHA選項可以使C++異
常機制捕獲結構化異常。
       還有一個編譯選項:/EHS,也是同步異常,但和/GX不同的是,/GX表示
假設 extern “C”的函數不會拋出C++異常。extern “C”函數一般是由C函數編寫的,因此它們不會拋出C++異常,當然也有例外。
       如果你的程序使用了異常機制,但沒使用其中一個異常處理編譯選項,編譯時將會產生一個警告信息:warning C4530 。
 
讓new 和malloc 拋出異常
       在VC默認情況下,new和malloc對於錯誤不會拋出異常。你可以通過使用
_set_new_handler安裝一個處理器,讓new針對錯誤拋出異常。通過調用
_set_new_mode讓malloc使用同一個處理器。
 
#i nclude <new.h>
int NewHandler( size_t size )
{
throw exception(“xxxx”);
}
int main()
{
_set_new_handler( NewHandler ) ;
_set_new_mode(1);
}
_set_new_mode(1) 表示malloc要使用new處理器。
 
異常的性能
       當拋出異常時,函數調用鏈將從此回溯搜索,尋找可以處理拋出的這類異常
的處理器。當然,如果沒有找到合適的處理器,進程將結束。如果處理器沒有找
到,調用棧將被釋放,所有的自動(局部)變量也將釋放,然後棧將被整理爲異
常處理器的上下文相關設備。因此異常開銷由維護一個異常處理器目錄和一個活動的自動變量表(它需要額外的代碼、內存,而且不論異常是否拋出,都會運行),
還得加上函數調用鏈的搜索、自動變量的解析和棧的調整(它只在拋出異常的時候需要執行)組成的。
       在很少拋出異常的情況下使用異常的開銷並不大,而且可能會提高性能。
看下面的代碼:
 
// 沒有使用異常,而是判斷指針
int check( int *pInt1 , int *pInt2 , int *pInt3 )
{
if( pInt1!= 0 && pInt2 != 0 && pInt3 != 0 )
{
       ......
}
else
return –1 ;
}
 
// 使用異常,如果參數爲NULL的機率很低 , 使用異常效率更高!
int check( int *pInt1 , int *pInt2 , int *pInt3 )
{
       try{
              ...
       }
       catch(...)
       {
              return –1 ;
       }
}
      
異常處理需要大量的額外操作,使得它並不適於經常運行的代碼(比如循環中)。
更詳細地說,catch塊有一些開銷,但是try塊有很少的開銷;因此只有在拋出異
常的時候纔會有很多的異常操作開銷。
 
 
什麼時候拋出異常
       首先說明一下什麼時候適合使用返回值,
1、用於非錯誤的狀態信息
2、用於大多數情況下可以隨意忽略而不會出現問題的錯誤
3、用於更易出現在循環中的錯誤,因爲異常的額外開銷,所以爲了更好的性能 ,使用返回值是一個更好的選擇
4、用於中間語言模塊中的錯誤,例如COM組件,必須使用返回值而不是異常。
 
拋出異常的時機應該是一個函數發現一個錯誤,這個錯誤能阻止程序正常的運行。當函數不能使用返回值時,如構造函數或重載的操作符‘=’等也要拋出異常。但是,應注意不能從析構函數中拋出異常,因爲異常可以阻止delete的調用,這樣就會有資源泄露:
delete pObject
相當於:
pObject->~CObject()
operator delete ( pObject )
因此析構函數中拋出異常會跳過operator delete的調用!
使用異常規範
處理異常的一問題是知道一個函數可以拋出哪一種類型的異常。函數拋出
的異常應該被認爲接口協議的一部分,和它的參數和返回值一樣。
       C++提供了異常規範,看下面的函數原型:
 
       Void NormalFunction() ;
       Void NoThrowFunction() throw() ;
       Void ThrowFunction() throw( Cexception ) ;
其中,NormalFunction 可以拋出任何異常,因爲它沒有異常規範,而NoThrow-
Function則不能拋出異常,這兩個函數的異常規範看起來是截然相反的。最後,
ThrowFunction可以拋出任何從Cexption派生來的異常對象。
 
異常規範的問題:
1、異常規範不完整:如果拋出的異常不是從異常規範類派生來的,線程會調用unexpected , 它會默認地結束進程。爲了創建一個完整的函數規範,你需要找到所有由這個函數直接拋出和所有這個函數調用的函數拋出的未處理的異常(如果這個函數內已處理了這些異常就不考慮)。
2、異常規範在編譯時不會檢查,如下面的代碼在編譯時不會出現警告:
void Funtion() throw()       // 規範:不會拋出異常
{
throw CException ; // 但實際上有拋出異常
}
3、不能和模板混合,因爲模板參數可以是任何類型的,你不能知道這種類型的成員函數可以拋出什麼異常;
4、如果你使用異常異常(即catch可以捕獲結構化異常),實際上任何函數都可以拋出一個轉化後的結構異常。
 
 
1、非MFC的C++異常應該通過引用來捕獲( catch( except &e ) )。使用引用捕
獲異常不需要刪除異常對象(因爲使用引用的捕獲的異常會在棧中傳送),   而且它保留了多態性(因此你捕獲的異常對象正是你拋出的異常對象)。使用指針捕獲異常需要你刪除對象。
2、MFC異常應該通過指針來捕獲。因爲它們從堆中分配,當你處理完異常之後,
       你需要調用Delete成員函數,如下所示:
       catch( CfileException *e )
       {
              e->Delete(); // 不要使用delete ,因爲一些MFC異常作爲靜態對象創建
       }
       你不能使用省略捕獲處理器捕獲MFC異常,因爲這會導致一個內存泄露。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章