C++异常处理
jopen
11年前
问题
- 抛出异常时发生了什么?
解答
#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; }</pre>
- throw;:重新抛出,是将全局区的异常对象继续沿着函数调用链向上传递;不会释放该异常对象;
- throw
匹配所有类型
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; }