C++異常處理

問題

  • 拋出異常時發生了什麼?數組

解答

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語句)時發生的事情:函數

  1. 在全局區建立異常對象的副本(對於類類型,調用複製構造函數,因此用做異常的類型複製構造函數必須可用) 即上方的'Exce: exce1 -> _exce1';測試

  2. 若是throw表達式沒有處在try塊中或者沒有匹配的catch子句,則執行堆棧展開:
    優化

    • 釋放函數所佔的內存空間this

    • 對於類類型的局部對象調用她們的析鉤函數(因此析鉤函數應該不拋出任何異常)來清理對象
      spa

  3. 不然執行第一個匹配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子句的形參類型徹底匹配纔會進入相應的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 /* 肯定是從新拋出 */

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

標準庫異常類繼承層次

相關文章
相關標籤/搜索