C++語言學習(十八)——異常處理

來源:互聯網
上載者:User

標籤:入口   記憶體   min   copy   申請   main   .cpp   相容   操作符   

C++語言學習(十八)——異常處理一、C語言異常處理

異常是指程式在運行過程中產生可預料的執行分支。如除0操作,數組訪問越界、要開啟的檔案不存在。
Bug是指程式中的錯誤,是不被預期的運行方式。如野指標、堆空間使用結束未釋放。
C語言中處理異常的方式一般是使用if....else...分支語句。

double divide(double a, double b){    const double delta = 0.000000000000001;    double ret = 0;    if( !((-delta < b) && (b < delta)) )    {        ret = a / b;    }    else    {        cout << "a is  devieded by zero" <<endl;    }    return ret;}

C語言通過setjmp和longjmp對異常處理進行最佳化。
int setjmp(jmp_buf env);
將上下文儲存到jmp_buf結構體中
void longjmp(jmp_buf env, int value);
從jmp_buf結構體中恢複setjmp儲存的上下文,最終從setjmp函數調用點返回,傳回值為value。

#include <iostream>#include <csetjmp>using namespace std;static jmp_buf env;double divide(double a, double b){    const double delta = 0.000000000000001;    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;}
二、C++語言異常處理機制1、異常處理簡介

C++語言中內建了異常處理的文法,try.....catch......。
try語句塊用來處理正常代碼邏輯,catch語句塊用來處理異常處理情況,throw拋出異常。在try語句塊拋出的異常在相應的catch語句塊捕獲處理。
同一個try語句塊可以對應多個catch語句塊,catch語句塊可以定義具體處理的異常類型,不同的類型的異常由不同的catch語句塊處理,try語句塊可以拋出任何類型的異常,catch(...)用於處理所有類型的異常,任何異常都只能被捕獲一次。
throw拋出的異常必須被catch處理,如果當前函數能夠處理異常,繼續執行;如果當前函數不能處理異常,函數停止執行並返回。未被處理的異常會順著函數調用棧向上傳遞,直到被處理為止,否則程式將停止執行。
異常處理的匹配:
A、異常拋出後從上到下嚴格匹配每個catch語句塊處理的類型,不能進行任何類型轉換。
B、catch(...)語句塊只能放到catch語句塊分支的最後位置。
異常處理的使用執行個體:

    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;    }

catch語句塊捕獲的異常重新解釋後可以拋出異常,拋出的異常在外層的try...catch中捕獲。

    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;    }

通常在catch語句塊中捕獲的異常重新解釋後可以再次拋出異常,工程實踐中通常用於統一異常類型,如通過捕獲第三方庫函數中拋出的異常,重新解釋後拋出統一的異常處理資訊。
異常的類型可以是自訂類型,自訂類型的異常匹配依舊是自上而下嚴格匹配,但由於賦值相容性原則在異常匹配中適用,所以匹配子類異常的catch語句塊放在catch分支的上部,匹配父類異常的catch語句塊放在catch分支的下部。

#include <iostream>using namespace std;class Parent{public:    Parent(int i):code(i)    {    }private:    int code;};class Child : public Parent{public:    Child(int i):Parent(i),code(i)    {    }private:    int code;};int main(int argc, char *argv[]){    try    {        Child child(1);        throw child;    }    catch(const Child& e)    {        cout << "catch(const Child& e)" << endl;    }    catch(const Parent& e)    {        cout << "catch(const Parent& e)" << endl;    }    return 0;}
2、STL中的異常處理

STL提供了實用的異常處理類,STL中的異常都是從exception類繼承而來,exception類只要有兩個分支,logic_error和runtime_error。logic_error用於處理常式中可避免邏輯錯誤,runtime_error用於處理常式中無法處理的惡性錯誤。

#include <iostream>#include <stdexcept>using namespace std;int main(int argc, char *argv[]){    int array[5] = {0};    for(int i = 0; i < 5; i++)    {        array[i] = i;    }    try    {        for(int i = 0; i < 10; i++)        {            if(i >= 5)            {                throw out_of_range("out of range");            }            else            {                cout << array[i] <<endl;            }        }    }    catch(const out_of_range& e)    {        cout << e.what() << endl;    }    return 0;}
3、try...catch特殊文法

try...catch語句用於分隔正常功能代碼與異常處理代碼。try...catch語句也可以將函數體分隔為兩部分。
函式宣告和定義時可以直接指定可能拋出的異常類型,異常聲明作為函數的一部分可以提高代碼可讀性。
函數異常聲明是一種與編譯器之間的契約,函式宣告異常後就只能拋出聲明的異常。如果拋出其它異常將會導致程式運行終止。也可以通過函數異常聲明定義無異常函數。

#include <iostream>using namespace std;//聲明拋出的異常類型為intvoid func(int i, int j)throw(int){    if(0 < j && j < 10)    {    }    else    {        throw 0;    }}void test(int i)try{    func(i,i);}catch(int i){    cout << "catch(int i): " << i << endl;}catch(...){    cout << "Exception:" << endl;}int main(int argc, char *argv[]){    test(10);    test(1);    return 0;}

上述代碼中,func函式宣告了拋出的異常類型為int,因此func函數只能拋出int類型異常,如果拋出其它類型異常將導致程式運行終止。即使test函數可以對拋出的其它類型異常進行捕獲,程式也會運行終止。
如果函數內部可能會拋出多種類型的異常,需要在函式宣告異常時指定聲明的異常類型,代碼如下:

#include <iostream>#include <string>using namespace std;//聲明拋出的異常類型為int,char,stringvoid func(int i, int j)throw(int,char,string){    if(0 < j && j < 10)    {        throw j;    }    if(10 < j && j < 100)    {        throw ‘A‘;    }    else    {        throw string("string exception.");    }}void test(int i)try{    func(i,i);}catch(int i){    cout << "catch(int i): " << i << endl;}catch(char c){    cout << "Exception:" << c << endl;}catch(string s){    cout << s << endl;}catch(...){    cout << "Exception:" << endl;}int main(int argc, char *argv[]){    test(115);//string exception.    test(1);//catch(int i): 1    test(20);//Exception:A    return 0;}

上述代碼中,func函數可以拋出多種類型的異常,test函數會捕獲func函數拋出的多種異常類型。

4、未被處理的異常

如果異常沒有被處理,terminate函數會被自動調用。terminate函數是整個程式釋放系統資源的最後機會。預設情況下,terminate函數調用abort庫函數終止程式。abort函數使得程式執行異常而立即退出。

#include <iostream>using namespace std;class Test{public:    Test()    {        cout << "Test()" << endl;    }    ~Test()    {        cout << "~Test()" << endl;    }};int main(int argc, char *argv[]){    static Test test;    throw 1;    return 0;}

上述代碼運行結果如下:

C++支援使用自訂的terminate函數實現替換預設的terminate函數實現。
自訂terminate函數的實現規則如下:
A、自訂一個無傳回值、無參數的函數
B、不能拋出任何異常
C、必須以某種方式結束當前程式
通過調用set_terminate函數可以設定自訂的terminate結束函數,其用法如下:
A、參數類型為void (*)()
B、傳回值為預設的terminate函數入口地址

#include <iostream>#include <cstdlib>using namespace std;class Test{public:    Test()    {        cout << "Test()" << endl;    }    ~Test()    {        cout << "~Test()" << endl;    }};void terminate_test(){    cout << "void terminate_test()" << endl;    exit(1);}int main(int argc, char *argv[]){    set_terminate(terminate_test);    static Test test;    throw 1;    return 0;}// output:// Test()// void terminate_test()// ~Test()

上述代碼在最終terminate_test結束函數中調用了exit(1),exit函數會確保程式中全域、待用資料區的對象被正確銷毀。如果使用abort函數替換exit函數,程式運行結果如下:

解構函式中拋出異常可能會導致最終結束函數terminate函數會被重複調用。

5、函數的異常規格說明

C++語言提供用於聲明函數拋出異常的文法聲明。異常聲明作為函式宣告的修飾符,位於函數參數表的後面。函數異常聲明的樣本如下:

//可能拋出任何異常void func1();//只能拋出的異常類型:char,intvoid func2() throw(char, int);//不拋出任何異常void func3() throw();

函數異常聲明的意義如下:
A、提示函數調用者必須做好異常處理的準備
B、提示函數的維護者不要拋出其它異常
C、函數異常規格說明是函數介面的一部分
如果函數拋出的異常類型不在函數異常聲明中,全域unexpected()函數會被調用。預設的unexpected()函數會調用全域的terminate函數,可以自訂函數替換預設的unexpected()函數實現。
自訂的unexpected()函數的實現規則如下:
A、自訂一個無傳回值、無參數的函數
B、能夠再次拋出異常,當異常符合觸發函數的異常規格說明時,恢複程式執行。否則,調用全域terminate函數結束程式。
通過調用set_unexpected函數可以設定自訂的unexpected()函數,用法如下:
A、參數類型為void (*)()
B、傳回值為預設的unexpected()函數入口地址。

#include <iostream>#include <cstdlib>using namespace std;void func() throw(int){    cout << "void func()throw(int)" << endl;    throw ‘A‘;}void unexpected_test(){    cout << "void unexpected_test()" << endl;    throw 1;}int main(int argc, char *argv[]){    set_unexpected(unexpected_test);    try    {        func();    }    catch(int)    {        cout << "catch(int)" << endl;    }    catch(char)    {        cout << "catch(char)" << endl;    }    return 0;}// output:// void func()throw(int)// void unexpected_test()// catch(int)

C++編譯器不一定對C++語言中函數異常規格說明進行支援。VC++編譯器不支援,G++編譯器支援。

6、動態記憶體申請異常

C語言中,malloc函數申請記憶體失敗時返回NULL值。
C++語言中,對於早期的C++編譯器,new關鍵字申請記憶體失敗時,返回NULL值;對於現代C++編譯器,new關鍵字申請記憶體失敗時,拋出std::bad_alloc異常。
C++語言規範中,new關鍵字的標準行為如下:
A、new在記憶體配置時,如果空間不足,會調用全域的new_handler函數,new_handler函數中拋出std::bad_alloc異常;如果成功,會在分配的空間調用建構函式建立對象,並返回對象的地址。
B、可以自訂new_handler函數,處理預設new記憶體配置失敗的情況。

#include <iostream>#include <cstdlib>using namespace std;void new_handler_test(){    cout << "void new_handler_test()" << endl;    cout << "No enough memory" << endl;    exit(1);}int main(int argc, char *argv[]){    set_new_handler(new_handler_test);    int* p = new(std::nothrow) int[10000000000];    return 0;}// output:// void new_handler_test()// No enough memory

上述代碼中,自訂new_handler函數,拋出異常時會調用。

#include <iostream>#include <cstdlib>#include <new>using namespace std;void new_handler_test(){    cout << "void new_handler_test()" << endl;    cout << "No enough memory" << endl;    exit(1);}int main(int argc, char *argv[]){    new_handler func = set_new_handler(new_handler_test);    cout << "func = " << func << endl;    if(func)    {        try        {            func();        }        catch(const bad_alloc& e)        {            cout << e.what() << endl;        }    }    return 0;}// func = 0

上述代碼是在G++編譯器、VC++編譯器下編譯執行後列印的結果,表明G++編譯器、VC++編譯器沒有設定預設的new_handler函數。如果C++編譯器(如BCC編譯器)設定有預設的new_handler函數,func函數執行時將會拋出bad_alloc異常,被捕獲後列印出bad_alloc異常的相關資訊。
不同的C++編譯器,new關鍵字申請動態記憶體失敗時表現不同。
工程實踐中,為了在不同C++編譯器間統一new關鍵字的行為,提高代碼的可移植性,解決方案如下:
A、重新定義全域的new/delete實現,不拋出任何異常;自訂new_handler函數,不拋出任何異常(不推薦)。
B、在類內重載new/delete操作符,不拋出任何異常。
C、單次動態記憶體分配時使用nothrow參數,指明new不拋出異常。

#include <iostream>#include <cstdlib>#include <new>using namespace std;class Test{    int m_data;public:    Test()    {        cout << "Test()" << endl;        m_data = 0;//異常    }    ~Test()    {        cout << "~Test()" << endl;    }    void* operator new (unsigned int size)    {        cout << "operator new: " << size << endl;        // return malloc(size);        return NULL;    }    void operator delete (void* p)    {        cout << "operator delete: " << p << endl;        free(p);    }    void* operator new[] (unsigned int size)    {        cout << "operator new[]: " << size << endl;        // return malloc(size);        return NULL;    }    void operator delete[] (void* p)    {        cout << "operator delete[]: " << p << endl;        free(p);    }};int main(int argc, char *argv[]){    Test* p = new Test();    cout << p << endl;    delete p;    return 0;}// output:// operator new: 4// Test()// 異常

上述代碼在執行new操作符函數後會調用Test建構函式,並在初始化m_data成員變數時拋出異常。為了確保不同C++編譯器在調用new關鍵字時具有相同的行為,需要在new失敗時不拋出異常,因此需要在new操作符增加函數的異常聲明。

#include <iostream>#include <cstdlib>#include <new>using namespace std;class Test{    int m_data;public:    Test()    {        cout << "Test()" << endl;        m_data = 0;//異常    }    ~Test()    {        cout << "~Test()" << endl;    }    void* operator new (unsigned int size) throw()    {        cout << "operator new: " << size << endl;        // return malloc(size);        return NULL;    }    void operator delete (void* p)    {        cout << "operator delete: " << p << endl;        free(p);    }    void* operator new[] (unsigned int size) throw()    {        cout << "operator new[]: " << size << endl;        // return malloc(size);        return NULL;    }    void operator delete[] (void* p)    {        cout << "operator delete[]: " << p << endl;        free(p);    }};int main(int argc, char *argv[]){    Test* p = new Test();    cout << p << endl;    delete p;    p = new Test[5];    cout << p << endl;    delete [] p;    return 0;}// output:// operator new: 4// 0// operator new[]: 24// 0

上述代碼對Test類的new和delete關鍵字進行了重載,統一了new失敗時的行為。

#include <iostream>using namespace std;int main(int argc, char *argv[]){    //不拋出異常    int* p = new(nothrow) int[1000000000];    cout << "p = " << p << endl;    delete [] p;    int array[2] = {0};    struct Test    {        int x;        int y;    };    //在棧空間建立對象    Test* pTest = new(array) Test();    pTest->x = 100;    pTest->y = 200;    cout << array[0] << endl;    cout << array[1] << endl;    //顯示析構    pTest->~Test();    return 0;}// output:// p = 0// 100// 200

上述代碼中使用nothrow關鍵字對象new進行限制,確保new建立對象失敗時不會拋出異常。new關鍵字也可以指定建立對象的地址空間,比如棧空間。

7、C++編譯器對new關鍵字的實現

不是所有的C++編譯器都遵循C++標準規範,C++編譯器可能重新定義new關鍵字的實現,並在實現中拋出bad_alloc異常。VC++編譯器對new關鍵字進行了重定義,new關鍵字在new.cpp檔案中進行了實現。

#ifdef _SYSCRT#include <cruntime.h>#include <crtdbg.h>#include <malloc.h>#include <new.h>#include <stdlib.h>#include <winheap.h>#include <rtcsup.h>#include <internal.h>void * operator new( size_t cb ){    void *res;    for (;;) {        //  allocate memory block        res = _heap_alloc(cb);        //  if successful allocation, return pointer to memory        if (res)            break;        //  call installed new handler        if (!_callnewh(cb))            break;        //  new handler was successful -- try to allocate again    }    RTCCALLBACK(_RTC_Allocate_hook, (res, cb, 0));    return res;}#else  /* _SYSCRT */#include <cstdlib>#include <new>_C_LIB_DECLint __cdecl _callnewh(size_t size) _THROW1(_STD bad_alloc);_END_C_LIB_DECLvoid *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)        {       // try to allocate size bytes        void *p;        while ((p = malloc(size)) == 0)                if (_callnewh(size) == 0)                {       // report no memory                static const std::bad_alloc nomem;                _RAISE(nomem);                }        return (p);        }/* * Copyright (c) 1992-2002 by P.J. Plauger.  ALL RIGHTS RESERVED. * Consult your license regarding permissions and restrictions. V3.13:0009 */#endif  /* _SYSCRT */

上述代碼顯示,在new失敗時預設拋出bad_alloc異常。

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.