C++異常處理

jopen 10年前發布 | 11K 次閱讀 C/C++開發 C/C++

問題

  • 拋出異常時發生了什么?
  • </ul>

    解答


    #include <stdio.h>
    #include <string.h>
    
    #define _mNT    throw()
    
    class Exce{
        char exceName[16];
    public:
        Exce(const char *__name)_mNT{
            strcpy(exceName,__name);
            printf("Exce: %s\n",exceName);
        }
    
        ~Exce()_mNT{ printf("Exce: ~%s\n",exceName); }
    
        Exce(const Exce & __exce)_mNT{
            exceName[0]='_';exceName[1]='\0';
            strcat(exceName,__exce.exceName);
            printf("Exce: %s -> %s\n",
                    __exce.exceName,
                    this->exceName);
        }
    
    };
    
    void exce1()throw(Exce){
        Exce exce11("exce1");
        throw exce11;
        return ;
    }
    
    void exce2()throw(Exce){
        Exce exce22("exce2");
        try{ exce1(); }
        catch(int __e){ ; }
        return ;
    }
    
    void exce3()throw(Exce){
        Exce exce33("exce3");
        try{ exce2(); }
        catch(const Exce &__e){ ; }
        return ;
    }
    
    int main(int argc,char *argv[]){
        exce3();
        return 0;
    }
    運行程序:
    Exce: exce3
    Exce: exce2
    Exce: exce1
    Exce: exce1 -> _exce1   
    Exce: ~exce1
    /* throw表達式沒有處在try塊中,所以執行堆棧展開 */
    Exce: ~exce2
    /* 雖然處在try快中但是沒有匹配的catch子句,所以繼續執行堆棧展開 */
    Exce: ~_exce1
    /* 找到匹配的catch子句,執行完畢后.處在全局區的異常對象會被釋放 */
    Exce: ~exce3
    所以,拋出異常(執行throw語句)時發生的事情:
    1. 在全局區創建異常對象的副本(對于類類型,調用復制構造函數,所以用作異常的類型復制構造函數必須可用) 即上方的'Exce: exce1 -> _exce1';
    2. 如果throw表達式沒有處在try塊中或者沒有匹配的catch子句,則執行堆棧展開:
      • 釋放函數所占的內存空間
      • 對于類類型的局部對象調用她們的析鉤函數(所以析鉤函數應該不拋出任何異常)來清理對象
    3. 否則執行catch子句,并且一般情況下執行完catch子句后,處在全局區的異常對象會被釋放(除非catch子句中使用了重新拋出'throw;)

    拋出時對指針解引用


    #include <stdio.h>
    
    class A{
    public:
        A(){ printf("A\n"); }
        A(const A &__a){ printf("A -> "); }
        virtual ~A(){ ; }
    };
    
    class B:public A{
    public:
        B():A(){ printf("B\n"); }
        B(const B &__b):A(){ printf("B ->"); }
        virtual ~B(){ ; }
    };
    
    int main(int argc,char *argv[]){
        B b;
        A *a=&b;
        try{
            throw *a;    
        }catch(const B &__b){
            printf("Catch: B\n");
        }catch(const A &__a){
            printf("Catch: A\n");
        }
    
        return 0;
    }
    throw *a;對a解引用,無論指針指向的實際類型是什么, 拋出的異常對象(也即在全局區創建的異常對象類型)總與指針的靜態類型相匹配
    所以 catch(const A &__a) 匹配


    匹配catch子句

    一般情況下,異常對象必須與catch子句的形參類型完全匹配才會進入相應的catch子句中,除了下列三種情況:

    • 允許非const到const的轉換,非const對象的throw可以與指定接受const引用的catch子句匹配;
    • 允許派生類型到基類型的轉換
    • 允許數組轉換為數組類型的指針,函數轉換為函數類型的指針

    重新拋出

    當catch子句中使用了重新拋出時,處在全局區的異常對象不會被釋放,如:


    #include <stdio.h>
    
    struct A{
        int a;
        A():a(0){ printf("A:%p\n",this); }
        A(const A &__a){ printf("A:%p->%p\n",&__a,this); }
        ~A(){ printf("A:~%p\n",this); }
    };
    
    void f(){
        A a1;
        throw a1;
        return ;
    }
    
    void f1(){
        try{ f(); }
        catch(A &__a1){ ++__a1.a; throw; }
        /* throw與throw __a1是不同 */
        return ;
    }
    
    void f2(){
        try{ f1(); }
        catch(A &__a){ printf("%d\n",__a.a); }
    }
    
    int main(int argc,char *argv[]){
        f2();
        return 0;
    }
    • throw;:重新拋出,是將全局區的異常對象繼續沿著函數調用鏈向上傳遞;不會釋放該異常對象;
    • throw __a1:此時根據__a1重新在全局區創建一個新的異常對象,然后將處在全局區的__a1釋放;然后在堆棧展開...balabala

    匹配所有類型

    catch(...){};匹配所有類型的異常對象,如果'catch(...)'處在catch子句的第一位,那么其他catch是不會得到機會的;

    函數測試塊

    用于捕獲構造函數初始化列表中的異常
    不過測試發現:會在捕獲處理后將初始化列表中發生的異常重新拋出('throw;'那種)


    #include <stdio.h>
    
    struct A{
        int a;
        A():a(0){ printf("A:%p\n",this); }
        A(const A &__a){ printf("A:%p->%p\n",&__a,this); }
        ~A(){ printf("A:~%p\n",this); }
    };
    
    int f(){
        A a1;
        throw a1;
        return 0;
    }
    
    class B{
        int a;
    public:
        B()try:a(f()){/* 注意try的位置,初始化列表之前 */
            ;
        }catch(...){/* 會捕獲初始化列表與構造函數體中拋出的異常,不過在處理后又會重新拋出 */
            printf("B:Catch\n");
        }
    };
    
    
    int main(int argc,char *argv[]){
        try{ B b; }
        catch(...){ printf("main:Catch\n"); }
        return 0;
    }
    
    /* 執行結果: */
    A:0x7fffc45fbae0
    A:0x7fffc45fbae0->0x1afd090
    A:~0x7fffc45fbae0
    B:Catch
    main:Catch    
    A:~0x1afd090 /* 確定是重新拋出 */    

    RAII

    資源分配即初始化,即通過一個類來包裝資源的分配與釋放,這樣可以保證異常發生時資源會被釋放

    異常說明


    void f()throw(Type)
    /* f 會拋出Type類型或其派生類型的異常 */
    void f()throw()
    /* f()不會拋出任何異常,此時編譯器可能會執行一些被可能拋出異常的代碼抑制的優化 */
    void f()
    /* f()會拋出任何類型的異常 */


    違反了異常說明

    如果拋出了不在異常說明列表中的異常,則會執行堆棧展開退出當前函數后直接調用標準庫函數 unexcepted()[默認調用 terminate()終止程序 ] ;而不會沿著函數調用鏈向上...如:


    #include <stdio.h>
    
    struct A{
        int a;
        A():a(0){ printf("A:%p\n",this); }
        A(const A &__a){ printf("A:%p->%p\n",&__a,this); }
        ~A(){ printf("A:~%p\n",this); }
    };
    
    int f()throw(){
        A a1;
        throw a1;
        return 0;
    }
    
    void f1(){
        A a2;
        f();
        return ;
    }
    
    
    int main(int argc,char *argv[]){
        try{ f1(); }
        catch(...){ printf("main:Catch\n"); }
        return 0;
    }

    繼承層次中的異常說明

    派生類虛函數異常說明中的異常列表?基類虛函數異常說明中的異常列表,這個主要是為了:

    當通過基類指針調用派生類虛函數,這條限制可以保證派生類虛函數不會拋出新的異常
    #include <stdio.h>
    
    class A1{ virtual ~A1(){ ; } };
    class B1:public A1{  };
    
    class A{
    public:
        virtual void print()throw(A1){
            return ;
        }
        virtual ~A(){ ; }
    };
    
    class B:public A{
    public:
        virtual void print()throw(B1){
            return ;
        }
        virtual ~B(){ ; }
    };
    
    int main(int argc,char *argv[]){
        B b; b.print();
        return 0;
    }
    只要滿足批注中的條件即可,如上例也編譯通過


    異常說明與析鉤函數


    class A{
    public:
        virtual void print()throw(A1){
            return ;
        }
        virtual ~A()throw(){ ; }
    };
    
    class B:public A{
    public:
        virtual void print()throw(B1){
            return ;
        }
        virtual ~B(){ ; }
    };
    因為 ~A() 不會拋出任何類型的異常,所以 ~B() 也不能拋出任何類型的異常,如上例編譯不會通過;


    異常說明與函數指針


    int (*fptr)()throw(int,double);
    /* fptr作為一個函數指針,指向著一個函數:
     * 該函數沒有參數,返回類型為int
     * 并且可能拋出int,double類型的異常 */
    int (*fptr1)()throw();


    當給函數指針賦值的時候,源指針異常聲明的類型列表?目的指針異常聲明的類型列表,這樣主要是為了保證:

    當通過目的指針調用函數時,函數拋出的異常不會多于目的函數指針異常列表中的異常
    但實際上,下列代碼編譯成功了:


    #include <stdio.h>
    
    
    int f()throw(int,double){
        throw 1;
        return 0;
    }
    
    int main(int argc,char *argv[]){
        int (*fptr1)()throw();
        fptr1=f;/* 這里賦值應該是失敗的... */
        try{ fptr1(); }
        /* fptr1的異常說明不會拋出任何異常,所以這里拋出異常時應該是
         * 調用unexpected()的;但實際上異常被捕獲了 */
        catch(...){ ; }
        return 0;
    }


 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!