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

标准库异常类继承层次

相关文章
相关标签/搜索