C++異常處理

來源:互聯網
上載者:User

標籤:oid   全域   函數介面   text   定義類   include   expec   alt   標準   

1、C語言異常處理1.1、異常的概念

異常:程式在運行過程中可能產生異常(是程式運行時可預料的執行分支),如:運行時除0的情況,需要開啟的外部檔案不存在的情況,數組訪問越界的情況...
Bug:bug是程式中的錯誤,是不可被預期運行方式,如:野指標、堆記憶體結束後未釋放、選擇排序無法處理長度為0的數組...

1.2、C語言的異常處理1.2.1經典方法:

if(判斷是否產生異常)
{
//正常代碼邏輯
}
else
{
//異常代碼邏輯
}

#include <iostream>#include <string>using namespace std;double divide(double a, double b, int* valid){    const double delta = 0.000000000000001;  //一般不要拿浮點數和0直接做比較    double ret = 0;    if( !((-delta < b) && (b < delta)) )    {        ret = a / b;        *valid = 1;    }    else    {        *valid = 0;    }    return ret;}int main(int argc, char *argv[]){       int valid = 0;    double r = divide(1, 0, &valid);    if( valid )    {        cout << "r = " << r << endl;    }    else    {        cout << "Divided by zero..." << endl;    }    return 0;}
1.2.2.setjmp()和longjmp()

int setjmp(jmp_buf env)
將當前上下文儲存在jmp_buf結構體中
void longjmp(jmp_buf env, int val)
從jmp_buf結構體中恢複setjmp()儲存的上下文
最終從setjmp函數調用點返回,傳回值為val

#include <iostream>#include <string>#include <csetjmp>using namespace std;/**缺陷:setjmp()和longjmp()的引入必然涉及到全域變數,暴力跳轉導致代碼可讀性降低***/static jmp_buf env;     //定義全域變數double divide(double a, double b){    const double delta = 0.000000000000001;     //一般不要拿浮點數和0直接做比較    double ret = 0;    if( !((-delta < b) && (b < delta)) )    {        ret = a / b;    }    else    {        longjmp(env, 1);    }    return ret;}int main(int argc, char *argv[]){       if( setjmp(env) == 0 )    {        double r = divide(1, 1);        cout << "r = " << r << endl;    }    else    {        cout << "Divided by zero..." << endl;    }    return 0;}

缺陷:setjmp()和longjmp()的引入必然涉及到全域變數,暴力跳轉導致代碼可讀性降低

2、C++中的異常處理(上)2.1 try、catch、throw

C++內建了異常處理的文法元素try、catch、throw
--try語句處理正常的代碼邏輯
--catch語句處理異常的情況
--C++通過throw語句拋出異常資訊
try語句中的異常由對應的catch語句處理
函數在運行時拋出(throw)一個異常到函數調用的地方(try語句內部),try語句就會將異常交給對應的catch語句去處理

#include <iostream>#include <string>using namespace std;/**函數在運行時拋出(throw)一個異常到函數調用的地方(try語句內部),try語句就會將異常交給對應的catch語句去處理**/double divide(double a, double b){    const double delta = 0.000000000000001;    double ret = 0;    if( !((-delta < b) && (b < delta)) )    {        ret = a / b;    }    else    {        throw 0;    }    return ret;}int main(int argc, char *argv[]){        try    {        double r = divide(1, 0);        cout << "r = " << r << endl;    }    catch(...)    {        cout << "Divided by zero..." << endl;    }    return 0;}
2.2 C++異常處理分析

--throw拋出的異常必須被catch處理
當前函數能夠處理異常,程式繼續往下執行
當前函數無法處理異常,則函數停止執行,並返回(未被處理的異常會順著函數調用棧向上傳播,直到被處理為止,否則程式將停止執行)

2.3自訂具體的異常類型

--不同的異常由不同的catch語句負責處理
--catch(...)用於處理所有類型的異常(只能被放在最後面)

#include <iostream>#include <string>using namespace std;/**異常拋出後,至上而下將嚴格的匹配每一個catch語句處理的類型,不進行任何類型的轉換**/void Demo1(){    try    {           throw ‘c‘;    }    catch(char c)    {        cout << "catch(char c)" << endl;    }    catch(short c)    {        cout << "catch(short c)" << endl;    }    catch(double c)    {        cout << "catch(double c)" << endl;    }    catch(...)    {        cout << "catch(...)" << endl;    }}void Demo2(){    throw string("D.T.Software");}int main(int argc, char *argv[]){        Demo1();    try    {        Demo2();    }    catch(char* s)    {        cout << "catch(char *s)" << endl;    }    catch(const char* cs)    {        cout << "catch(const char *cs)" << endl;    }    catch(string ss)    {        cout << "catch(string ss)" << endl;    }    return 0;}

注意:任何異常都只能被捕獲(catch)一次,異常拋出後,捕獲時至上而下將嚴格的匹配每一個catch語句處理的類型,不進行任何類型的轉換

3、C++中的異常處理(下)3.1、catch重新解釋

catch中捕獲的異常可以被重新解釋拋出,catch拋出的異常需要外層的try...catch...捕獲
為什麼要重新拋出異常?
實際工程中我們可以對第三方庫中拋出的異常進行捕獲、重新解釋(統一異常類型,方便代碼問題定位),然後再拋出

#include <iostream>#include <string>using namespace std;void Demo(){    try    {        try        {            throw ‘c‘;        }        catch(int i)        {            cout << "Inner: catch(int i)" << endl;            throw i;        }        catch(...)        {            cout << "Inner: catch(...)" << endl;            throw;        }    }    catch(...)    {        cout << "Outer: catch(...)" << endl;    }}/*    假設: 當前的函數是第三方庫中的函數,因此,我們無法修改原始碼    函數名: void func(int i)    拋出異常的類型: int                        -1 ==》 參數異常                        -2 ==》 運行異常                        -3 ==》 逾時異常*/void func(int i){    if( i < 0 )    {        throw -1;    }    if( i > 100 )    {        throw -2;    }    if( i == 11 )    {        throw -3;    }    cout << "Run func..." << endl;}void MyFunc(int i)  //調用第三方庫函數,捕獲並重新解釋異常,然後拋出{    try    {        func(i);    }    catch(int i)    {        switch(i)        {            case -1:                throw "Invalid Parameter";      //捕獲異常並重新解釋並拋出                break;            case -2:                throw "Runtime Exception";                break;            case -3:                throw "Timeout Exception";                break;        }    }}int main(int argc, char *argv[]){    Demo();    try    {        MyFunc(11);    }    catch(const char* cs)    {        cout << "Exception Info: " << cs << endl;    }    return 0;}

注意:
(1)異常的類型可以是自訂類類型,對於類類型異常的匹配依舊是至上而下、嚴格匹配
(2)賦值相容原則在異常匹配中依然適用,一般而言
--匹配子類異常的catch放在上部
--匹配父類異常的catch放在下部

#include <iostream>#include <string>using namespace std;class Base{};//異常的類型可以是自訂類類型class Exception : public Base{    int m_id;    string m_desc;public:    Exception(int id, string desc)    {        m_id = id;        m_desc = desc;    }    int id() const    {        return m_id;    }    string description() const    {        return m_desc;    }};/*    假設: 當前的函數式第三方庫中的函數,因此,我們無法修改原始碼    函數名: void func(int i)    拋出異常的類型: int                        -1 ==》 參數異常                        -2 ==》 運行異常                        -3 ==》 逾時異常*/void func(int i){    if( i < 0 )    {        throw -1;    }    if( i > 100 )    {        throw -2;    }    if( i == 11 )    {        throw -3;    }    cout << "Run func..." << endl;}void MyFunc(int i){    try    {        func(i);    }    catch(int i)    {        switch(i)        {            case -1:                throw Exception(-1, "Invalid Parameter");                break;            case -2:                throw Exception(-2, "Runtime Exception");                break;            case -3:                throw Exception(-3, "Timeout Exception");                break;        }    }}int main(int argc, char *argv[]){    try    {        MyFunc(11);    }    //在定義catch語句塊時推薦使用引用作為參數(防止拷貝構造)    // 賦值相容原則在異常匹配中依然適用,一般而言    catch(const Exception& e)   // 匹配子類異常的catch放在上部    {        cout << "Exception Info: " << endl;        cout << "   ID: " << e.id() << endl;        cout << "   Description: " << e.description() << endl;    }    catch(const Base& e)        // 匹配父類異常的catch放在下部    {        cout << "catch(const Base& e)" << endl;    }    return 0;}
3.2工程建議:

在工程中會定義一系列的異常類,每個類代表工程中可能出現的一種異常類型
代碼複用時可能需要重新解釋不同的異常類
在定義catch語句塊時推薦使用引用作為參數(防止拷貝構造)

3.3、標準庫異常類族

(1)C++標準庫中提供了實用異常類族,都是從exception類派生的,主要有兩個分支
--logic_error(常用於程式中可避免的邏輯錯誤)
--runtime_error(常用於程式中無法避免的惡性錯誤)
標準庫中的異常:

4.異常處理深度解析問題1:main函數拋出異常

main函數中跑出異常會發生什嗎?如果異常不處理會傳到哪裡?

#include <iostream>using namespace std;class Test {public:    Test()     {        cout << "Test()";         cout << endl;    }    ~Test()     {        cout << "~Test()";         cout << endl;    }};int main(){    static Test t;    throw 1;    return 0;}

實驗結果證明,異常不被處理會導致程式會異常結束,並列印異常語句。
那麼異常語句是哪裡列印的?
如果異常無法被處理,terminate()函數會被自動調用,該函數用於結束異常,同時在terminate()函數中會調用庫函數abort()函數終止程式(abort函數使得程式執行異常並立即退出)。
C++文法支援自訂terminate()函數的實現:
(1)定義一個無傳回值無參數的函數(函數類型為void(*)()類型)
a)不能拋出異常
b)必須以某種方式結束當前程式(abort/exit/…)
(2)調用set_terminate註冊自訂的terminate()函數
a)傳回值為預設的terminate函數入口地址。

#include <iostream>#include <cstdlib>#include <exception>using namespace std;void my_terminate(){    cout << "void my_terminate()" << endl;    exit(1);}class Test {public:    Test()     {        cout << "Test()";         cout << endl;    }    ~Test()     {        cout << "~Test()";         cout << endl;    }};int main(){    set_terminate(my_terminate);    static Test t;    throw 1;    return 0;}
問題2:析構函拋出異常

解構函式中拋出異常會怎麼樣?

#include <iostream>#include <cstdlib>#include <exception>using namespace std;void my_terminate(){    cout << "void my_terminate()" << endl;    // exit(1);    abort();    // C++ 標準庫中terminate()函數調用的為abort直接結束程式,不會再去調用解構函式,防止解構函式中還有異常扔出}class Test {public:    Test()     {        cout << "Test()";         cout << endl;    }    ~Test()     {        cout << "~Test()";         cout << endl;        throw 2;    // terminate函數是整個程式釋放資源的最後機會                    // 解構函式中不能拋出異常,會導致terminate函數被多次調用,造成資源重複釋放    }};int main(){    set_terminate(my_terminate);    static Test t;    throw 1;    return 0;}

實驗結果證明在Linux這樣比較穩定的環境中解構函式中拋出異常會調用terminate()函數。貌似沒有什麼問題,但對於某些嵌入式系統,可能導致系統的不穩定。
結論:
terminate函數是整個程式釋放資源的最後機會。
解構函式中不能拋出異常,會導致terminate函數被多次調用,造成資源重複釋放。
C++ 標準庫中terminate()函數調用的為abort函數,直接結束程式,不會再去調用解構函式,防止解構函式中還有異常扔出

5.函數異常規格說明5.1如何判斷一個函數是否 會拋出異常,會拋出那些異常?

C++文法提供了用於申明函數所拋出的異常,異常做為函式宣告的 修飾符寫函式宣告的後面。
// 可能拋出任何異常
void fun(void) ;
// 只能拋出int型異常
void fun(void) throw(int);
// 不能拋出異常
void fun(void) throw();

#include <iostream>using namespace std;void func() throw(int){    cout << "func()";    cout << endl;    throw ‘c‘;}int main(){    try     {        func();    }     catch(int)     {        cout << "catch(int)";        cout << endl;    }     catch(char)     {        cout << "catch(char)";        cout << endl;    }    return 0;}

異常規格說明的意義:
-提示函數調用這必須做好異常處理的準備
-提示函數的維護者不要拋出其他異常
-異常規格 說明是函數介面的一部分。

5.2如果拋出的異常不在申明列表裡會發生什嗎?

函數拋出的異常不在規格說明中,全域函數unexpected()會被調用
預設的unexpected()函數會調用全域的terminate()函數
可以自訂unexpected()函數
--函數類型void(*)(void)
--能夠再次拋出異常
a)在次拋出的異常符合觸發函數的異常規格函數時,程式恢複執行
b)否則,調用terminate()函數結束程式
--調用set_unexpected()函數設定自訂的異常函數,傳回值為預設的unexpected函數入口地址。
注意不是所有C++編譯器都支援這個標準行為,其中vs就不支援(會直接處理拋出的異常,儘管其不在異常規格申明中)。

#include <iostream>#include <cstdlib>#include <exception>/**函數拋出的異常不在規格說明中,全域函數unexpected()會被調用    預設的unexpected()函數會調用全域的terminate()函數    可以自訂unexpected()函數        --函數類型void(*)(void)        --能夠再次拋出異常            a)在次拋出的異常符合觸發函數的異常規格函數時,程式恢複執行            b)否則,調用terminate()函數結束程式        --調用set_unexpected()函數設定自訂的異常函數,傳回值為預設的unexpected函數入口地址。    注意不是所有C++編譯器都支援這個標準行為,其中vs就不支援(會直接處理拋出的異常,儘管其不在異常規格申明中)。**/using namespace std;void my_unexpected(){    cout << "void my_unexpected()" << endl;    // exit(1);    throw 1;}void func() throw(int){    cout << "func()";    cout << endl;    throw ‘c‘;}int main(){    set_unexpected(my_unexpected);    try     {        func();    }     catch(int)     {        cout << "catch(int)";        cout << endl;    }     catch(char)     {        cout << "catch(char)";        cout << endl;    }    return 0;}
6.try ...catch的其他寫法

1、try…catch用於分隔正常邏輯的代碼和異常處理代碼,可以直接將函數實現分隔為兩部分。
2、函式宣告和定義時可以直接指定拋出的異常類型,異常聲明成為函數的一部分可以提高代碼的可讀性。

#include <iostream>#include <string>using namespace std;int func(int i, int j) throw(int, char){    if( (0 < j) && (j < 10) )    {        return (i + j);    }    else    {        throw ‘0‘;    }}void test(int i) try{    cout << "func(i, i) = " << func(i, i) << endl;}catch(int i){    cout << "Exception: " << i << endl;}catch(...){    cout << "Exception..." << endl;}int main(int argc, char *argv[]){    test(5);    test(10);    return 0;}

顯然這種寫法可讀性降低。
本文參考唐老師課程,特此鳴謝。

C++異常處理

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.