抛出异常时发生了什么?数组
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; }