1. 程式人生 > >c++ 程式中實現丟擲異常

c++ 程式中實現丟擲異常

https://blog.csdn.net/WBENTELY/article/details/70240526

丟擲異常(也稱為拋棄異常)即檢測是否產生異常,在C++中,其採用throw語句來實現,如果檢測到產生異常,則丟擲異常。該語句的格式為:
throw 表示式;
    如果在try語句塊的程式段中(包括在其中呼叫的函式)發現了異常,且拋棄了該異常,則這個異常就可以被try語句塊後的某個catch語句所捕獲並處理,捕獲和處理的條件是被拋棄的異常的型別與catch語句的異常型別相匹配。由於C++使用資料型別來區分不同的異常,因此在判斷異常時,throw語句中的表示式的值就沒有實際意義,而表示式的型別就特別重要。
【範例20-2】處理除數為0的異常。該範例將上述除數為0的異常可以用try/catch語句來捕獲異常,並使用throw語句來丟擲異常,從而實現異常處理,實現程式碼如程式碼清單20-2所示。
程式碼清單20-2
1    #include<iostream.h>                                 //包含標頭檔案
2    #include<stdlib.h>
3    double fuc(double x, double y)                        //定義函式
4    {
5        if(y==0)
6        {
7            throw y;                                    //除數為0,丟擲異常
8        }
9        return x/y;                                    //否則返回兩個數的商
10    }
11    void main()
12    {
13        double res;
14        try                                            //定義異常
15        {
16            res=fuc(2,3);
17            cout<<"The result of x/y is : "<<res<<endl;
18            res=fuc(4,0);                                //出現異常
19        }
20        catch(double)                                    //捕獲並處理異常
21        {
22            cerr<<"error of dividing zero.\n";
23            exit(1);                                    //異常退出程式
24        }
25    }
【執行結果】在Visual C++中新建一個【C++ Source File】檔案,輸入上述的程式碼,編譯無誤後執行。
【範例解析】上述程式碼中,在主函式main()的第14~19行中使用了try語句定義異常,其中包含3條有可能出現異常的語句,它們為呼叫兩個數相除的函式。在程式碼的第20~24行定義了異常處理,即捕獲異常後執行該段程式碼中的語句。此外,在函式fuc()的程式碼5~8行通過throw語句丟擲異常。

注意:一般來說,throw語句通常與try- catch或try-finally語句一起使用,可以使用throw語句顯式引發異常。
////////////

c++ try_catch
 
1、基礎介紹
try
{
//程式中丟擲異常
throw value;
}
catch(valuetype v)
{
//例外處理程式段
}
語法小結:throw丟擲值,catch接受,當然,throw必須在“try語句塊”中才有效。

2、深入throw:
(i)、程式接受到throw語句後就會自動呼叫析構器,把該域(try後的括號內)物件clean up,然後再進
入catch語句(如果在迴圈體中就退出迴圈)。

這種機制會引起一些致命的錯誤,比如,當“類”有指標成員變數時(又是指標!),在 “類的構建器
”中的throw語句引起的退出,會導致這個指標所指向的物件沒有被析構。這裡很基礎,就不深入了,提
示一下,把指標改為類就行了,比如模板類來代替指標,在模板類的內部設定一個解構函式。

(ii)、語句“throw;”丟擲一個無法被捕獲的異常,即使是catch(...)也不能捕捉到,這時進入終止函式
,見下catch。

3、深入catch:
一般的catch出現的形式是:
try{}
catch(except1&){}
catch(except2&){}
catch(...){} //接受所有異常
一般都寫成引用(except1&),原因很簡單,效率。

問題a:丟擲異常,但是catch不到異常怎麼辦?(注意沒有Java類似的finally語句
在catch沒有捕獲到匹配的異常的時候,會呼叫預設的終止函式。可以呼叫set_terminate()來設定終止函式,引數是一個函式指標,型別是:void (*terminate)()。

到這裡,可以題個問題:“沒有try-catch,直接在程式中"throw;",會怎麼樣?”


其他一些技巧:
4、try一個函式體,形式如下
void fun(type1,type2) try----try放在函式體後
{
   函式定義
}
catch(typeX){}
這個用法的效果就相當於:
void fun() 
{
   try{函式定義}
}


5、throw一個函式體,形式如下:
void fun (); // 能丟擲任何型別的異常
void fun () throw(except1,except2,except3) 
               // 後面括號裡面是一個異常引數表,本例中只能丟擲這3中異常
void fun () throw()   // 引數表為空,不能丟擲異常

問題b:假設fun()中丟擲了一個不在“異常引數表”中的異常,會怎麼樣?

答:呼叫set_terminate()中設定的終止函式。然而,這只是表面現象,實際上是呼叫預設的unexpected()函式,然而這個預設的unexpected()呼叫了set_terminate()中設定的終止函式。可以用set_unexpected()來設定 unexpected,就像set_terminate()一樣的用法,但是在設定了新的“unexpected()”之後,就不會再呼叫 set_terminater中設定的終止函數了。

這個語法是很有用的,因為在用別人的程式碼時,不知道哪個地方會呼叫什麼函式又會丟擲什麼異常,用一個異常引數表在申明時限制一下,很實用。

 

 

c++ try catch 問題 :

try{} catch(…){}

以前都是用try{} catch(…){}來捕獲C++中一些意想不到的異常, 今天看了Winhack的帖子才知道,這種方法在VC中其實是靠不住的。例如下面的程式碼:

  1. try
  2. {
  3. BYTE* pch ;
  4. pch = ( BYTE* )00001234 ;   //給予一個非法地址
  5. *pch = 6 ; //對非法地址賦值,會造成Access Violation 異常
  6. }
  7. catch(...)
  8. {
  9. AfxMessageBox( "catched" ) ;
  10. }

這段程式碼在debug下沒有問題,異常會被捕獲,會彈出”catched”的訊息框。 但在Release方式下如果選擇了編譯器程式碼優化選項,則VC編譯器會去搜索try塊中的程式碼, 如果沒有找到throw程式碼, 他就會認為try catch結構是多餘的, 給優化掉。 這樣造成在Release模式下,上述程式碼中的異常不能被捕獲,從而迫使程式彈出錯誤提示框退出。

那麼能否在release程式碼優化狀態下捕獲這個異常呢, 答案是有的。 就是__try, __except結構, 上述程式碼如果改成如下程式碼異常即可捕獲。

  1. __try
  2. {
  3. BYTE* pch ;
  4. pch = ( BYTE* )00001234 ;   //給予一個非法地址
  5. *pch = 6 ; //對非法地址賦值,會造成Access Violation 異常
  6. }
  7. __except( EXCEPTION_EXECUTE_HANDLER )
  8. {
  9. AfxMessageBox( "catched" ) ;
  10. }

但是用__try, __except塊還有問題, 就是這個不是C++標準, 而是Windows平臺特有的擴充套件。 而且如果在使用過程中涉及區域性物件解構函式的呼叫,則會出現C2712 的編譯錯誤。 那麼還有沒有別的辦法呢?

當然有, 就是仍然使用C++標準的try{}catch(..){}, 但在編譯命令列中加入 /EHa 的引數。這樣VC編譯器不會把try catch模組給優化掉了。

一篇比較好的英文文章談這個問題: http://members.cox.net/doug_web/eh.htm


 

 C++中catch(…)如何使用:
上一篇文章中詳細講了講C++異常處理模型的trycatch使用語法,其中catch關鍵字是用來定義catch block的,它後面帶一個引數,用來與異常物件的資料型別進行匹配。注意catch關鍵字只能定義一個引數,因此每個catch block只能是一種資料型別的異常物件的錯誤處理模組。如果要想使一個catch block能抓獲多種資料型別的異常物件的話,怎麼辦?C++標準中定義了一種特殊的catch用法,那就是” catch(…)”。

感性認識

1、catch(…)到底是一個什麼樣的東東,先來個感性認識吧!看例子先:

int main()
{
try
{
cout << "在 try block 中, 準備丟擲一個異常." << endl;
//這裡丟擲一個異常(其中異常物件的資料型別是int,值為1)
throw 1;
}
//catch( int& value )
//注意這裡catch語句
catch( …)
{
cout << "在 catch(…) block 中, 丟擲的int型別的異常物件被處理" << endl;
}
}

  2、哈哈!int型別的異常被catch(…)抓獲了,再來另一個例子:

int main()
{
try
{
cout << "在 try block 中, 準備丟擲一個異常." << endl;
//這裡丟擲一個異常(其中異常物件的資料型別是double,值為0.5)
throw 0.5;
}
//catch( double& value )
//注意這裡catch語句
catch( …)
{
cout << "在 catch(…) block 中, double型別的異常物件也被處理" << endl;
}
}

   3、同樣,double型別的異常物件也被catch(…)塊抓獲了。是的,catch(..)能匹配成功所有的資料型別的異常物件,包括C++語言提 供所有的原生資料型別的異常物件,如int、double,還有char*、int*這樣的指標型別,另外還有陣列型別的異常物件。同時也包括所有自定義 的抽象資料型別。例程如下:

int main()
{
try
{
cout << "在 try block 中, 準備丟擲一個異常." << endl;
//這裡丟擲一個異常(其中異常物件的資料型別是char*)
char* p=0;
throw p;
}
//catch( char* value )
//注意這裡catch語句
catch( …)
{
cout << "在 catch(…) block 中, char*型別的異常物件也被處理" << endl;
}
}


int main()
{
try
{
cout << "在 try block 中, 準備丟擲一個異常." << endl;
//這裡丟擲一個異常(其中異常物件的資料型別是int[])
int a[4];
throw a;
}
//catch( int value[] )
//注意這裡catch語句
catch( …)
{
cout << "在 catch(…) block 中, int[]型別的異常物件也被處理" << endl;
}
}

  4、對於抽象資料型別的異常物件。catch(…)同樣有效,例程如下:

class MyException
{
public:
protected:
int code;
};

int main()
{
try
{
cout << "在 try block 中, 準備丟擲一個異常." << endl;
//這裡丟擲一個異常(其中異常物件的資料型別是MyException)
throw MyException();
}
//catch(MyException& value )
//注意這裡catch語句
catch( …)
{
cout << "在catch(…) block中, MyException型別的異常物件被處理" << endl;
}
}
對catch(…)有點迷糊?
1、究竟對catch(…)有什麼迷糊呢?還是看例子先吧!
void main()
{
int* p = 0;

try
{
// 注意:下面這條語句雖然不是throw語句,但它在執行時會導致系統
// 出現一個儲存保護錯誤的異常(access violation exception)
*p = 13; // causes an access violation exception;
}
catch(...)
{
//catch(…)能抓獲住上面的access violation exception異常嗎?
cout << "在catch(…) block中" << endl;
}
}

  請問上面的程式執行時會出現什麼結果嗎?catch(…)能抓獲住系統中出現的access violation exception異常嗎?朋友們!和我們的主人公阿愚一樣,自己動手去測試一把!
結果又如何呢?實際上它有兩種不同的執行結果,在window2000系統下用VC來測試執行這個小程式時,發現程式能輸出"在catch(…) block中"的語句在螢幕上,也即catch(…) 能成功抓獲住系統中出現的access violation exception異常,很厲害吧!但如果這個同樣的程式在Linux下用gcc編譯後執行時,程式將會出現崩潰,並在螢幕上輸出”segment fault”的錯誤資訊。

主人公阿愚有點急了,也開始有點迷糊了,為什麼?為什麼?為什麼同樣一個程式在兩種不同的系統上有不同的表現呢?其原因就是:對於這種由於硬體或操作 系統出現的系統異常(例如說被零除、記憶體儲存控制異常、頁錯誤等等)時,window2000系統有一個叫做結構化異常處理(Structured Exception Handling,SEH)的機制,這個東東太厲害了,它能和VC中的C++異常處理模型很好的結合上(實際上VC實現的C++異常處理模型很大程度上建 立在SEH機制之上的,或者說它是SEH的擴充套件,後面文章中會詳細闡述並分析這個久富盛名的SEH,看看catch(…)是如何神奇接管住這種系統異常出 現後的程式控制流的,不過這都是後話)。而在Linux系統下,系統異常是由訊號處理程式設計方法來控制的(訊號處理程式設計,signal processing progamming。在介紹unix和linux下如何程式設計的書籍中,都會有對訊號處理程式設計詳細的介紹,當然執著的主人公阿愚肯定對它也不會放過,會深 入到unix沿襲下來的訊號處理程式設計內部的實現機制,並嘗試完善改進它,使它也能夠較好地和C++異常處理模型結合上)。

那麼C++標準中對於這種同一個程式有不同的執行結果有何解釋呢?這裡需要注意的是,window2000系統下catch(…)能捕獲住系統異常, 這完全是它自己的擴充套件。在C++標準中並沒有要求到這一點,它只規定catch(…)必須能捕獲程式中所有通過throw語句丟擲的異常。因此上面的這個 程式在linux系統下的執行結果也完全是符合C++標準的。雖然大家也必須承認window2000系統下對C++異常處理模型的這種擴充套件確實是一個很 不錯的完善,極大得提高了程式的安全性。

為什麼要用catch(…)這個東東?

程式設計師朋友們也許會說,這還有問嗎?這篇文章的一開始不就講到了嗎?catch(…)能夠捕獲多種資料型別的異常物件,所以它提供給程式設計師一種對異常 物件更好的控制手段,使開發的軟體系統有很好的可靠性。因此一個比較有經驗的程式設計師通常會這樣組織編寫它的程式碼模組,如下:

void Func()
{
try
{
// 這裡的程式程式碼完成真正複雜的計算工作,這些程式碼在執行過程中
// 有可能丟擲DataType1、DataType2和DataType3型別的異常物件。
}
catch(DataType1& d1)
{
}
catch(DataType2& d2)
{
}
catch(DataType3& d3)
{
}
// 注意上面try block中可能丟擲的DataType1、DataType2和DataType3三
// 種類型的異常物件在前面都已經有對應的catch block來處理。但為什麼
// 還要在最後再定義一個catch(…) block呢?這就是為了有更好的安全性和
// 可靠性,避免上面的try block丟擲了其它未考慮到的異常物件時導致的程
// 序出現意外崩潰的嚴重後果,而且這在用VC開發的系統上更特別有效,因
// 為catch(…)能捕獲系統出現的異常,而系統異常往往令程式設計師頭痛了,現
// 在系統一般都比較複雜,而且由很多人共同開發,一不小心就會導致一個
// 指標變數指向了其它非法區域,結果意外災難不幸發生了。catch(…)為這種
// 潛在的隱患提供了一種有效的補救措施。
catch(…)

}
}

還有,特別是VC程式設計師為了使開發的系統有更好的可靠性,往往在應用程式的入口函式中(如MFC框架的開發環境下 CXXXApp::InitInstance())和工作執行緒的入口函式中加上一個頂層的trycatch塊,並且使用catch(…)來捕獲一切所有的 異常,如下:

BOOL CXXXApp::InitInstance()
{
if (!AfxSocketInit())
{
AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
return FALSE;
}

AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
// of your final executable, you should remove from the following
// the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls(); // Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic(); // Call this when linking to MFC statically
#endif


// 注意這裡有一個頂層的trycatch塊,並且使用catch(…)來捕獲一切所有的異常
try
{
CXXXDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK) 
{
// TODO: Place code here to handle when the dialog is
// dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
// dismissed with Cancel
}
}
catch(…)
{
// dump出系統的一些重要資訊,並通知管理員查找出現意外異常的原因。
// 同時想辦法恢復系統,例如說重新啟動應用程式等
}

// Since the dialog has been closed, return FALSE so that we exit the
// application, rather than start the application's message pump.
return FALSE;
}

   通過上面的例程和分析可以得出,由於catch(…)能夠捕獲所有資料型別的異常物件,所以在恰當的地方使用catch(…)確實可以使軟體系統有著更 好的可靠性。這確實是大家使用catch(…)這個東東最好的理由。但不要誤會的是,在C++異常處理模型中,不只有catch(…)方法能夠捕獲幾乎所 有型別的異常物件(也許有其它更好的方法,在下一篇文章中主人公阿愚帶大家一同去探討一下),可C++標準中為什麼會想到定義這樣一個catch(…) 呢?有過Java或C#程式設計開發經驗的程式設計師會發現,在它們的異常處理模型中,並沒有這樣類似的一種語法,可這裡不得不再次強調的是,java中的異常處 理模型是C++中的異常處理模型的完善改進版,可它反而沒有了catch(…),為何呢?還是先去看看下一章吧,“C++的異常處理和麵向物件的緊密關係 ”。也許大家能找到一個似乎合理的原因。