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語句)時發生的事情:
- 在全局區創建異常對象的副本(對于類類型,調用復制構造函數,所以用作異常的類型復制構造函數必須可用) 即上方的'Exce: exce1 -> _exce1';
- 如果throw表達式沒有處在try塊中或者沒有匹配的catch子句,則執行堆棧展開:
- 釋放函數所占的內存空間
- 對于類類型的局部對象調用她們的析鉤函數(所以析鉤函數應該不拋出任何異常)來清理對象
- 否則執行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 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!