拋出異常時發生了什麼?數組
class ExceptionItem{ string _ItemName; public: ExceptionItem(const string &itemName): _ItemName(itemName) { printf("構造: %s\n",_ItemName.c_str()); } ExceptionItem(const ExceptionItem &item): _ItemName(item._ItemName) { printf("複製: %s->%s\n",item._ItemName.c_str(),this->_ItemName.c_str()); } ~ExceptionItem(){ printf("析構: %s\n",_ItemName.c_str()); } string toString()const{ return _ItemName; } }; void func1(){ ExceptionItem item("func1"); throw item; } void func2(){ ExceptionItem item("func2"); try{ func1(); } catch(int e){ ; } } void func3(){ ExceptionItem item("func3"); try{ func2(); } catch(const ExceptionItem &item){ printf("捕捉: %s\n",item.toString().c_str()); throw; } } int main(int argc,char *argv[]){ try{ func3(); }catch(const ExceptionItem &item){ printf("捕捉: %s\n",item.toString().c_str()); } printf("結束\n"); }
因此,拋出異常(執行throw語句)時發生的事情:函數
在全局區建立異常對象的副本(對於類類型,調用複製構造函數,因此用做異常的類型複製構造函數必須可用) 即上方的'Exce: exce1 -> _exce1';測試
若是throw表達式沒有處在try塊中或者沒有匹配的catch子句,則執行堆棧展開:
優化
釋放函數所佔的內存空間this
對於類類型的局部對象調用她們的析鉤函數(因此析鉤函數應該不拋出任何異常)來清理對象
spa
不然執行第一個匹配catch子句,而且通常狀況下執行完catch子句後,處在全局區的異常對象會被釋放(除非catch子句中使用了從新拋出'throw;).net
#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解引用,不管指針指向的實際類型是什麼, 拋出的異常對象(也即在全局區建立的異常對象類型)總與指針的靜態類型相匹配;
因此此時執行 A::A(const A&) 建立拋出的異常對象,而後匹配 catch(const A &__a) 子句.指針
通常狀況下,異常對象必須與catch子句的形參類型徹底匹配纔會進入相應的catch子句中,除了下列三種狀況:code
容許非const到const的轉換,非const對象的throw能夠與指定接受const引用的catch子句匹配;對象
容許派生類型到基類型的轉換
容許數組轉換爲數組類型的指針,函數轉換爲函數類型的指針,如:
B b; // try{ // throw b; // }catch(const A &__b){ /* 被匹配 */ // printf("Catch: B\n"); // }catch(const B &__a){ // printf("Catch: A\n"); // } try{ throw &b; }catch(A *a){ /* 被匹配 */ puts("catch1"); }catch(B *b){ puts("catch2"); }
當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 /* 肯定是從新拋出 */
資源分配即初始化,即經過一個類來包裝資源的分配與釋放,這樣能夠保證異常發生時資源會被釋放;
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; }