C++异常处理

分享到:

问题

  • 抛出异常时发生了什么?

解答


#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语句)时发生的事情:
  1. 在全局区创建异常对象的副本(对于类类型,调用复制构造函数,所以用作异常的类型复制构造函数必须可用) 即上方的'Exce: exce1 -> _exce1';
  2. 如果throw表达式没有处在try块中或者没有匹配的catch子句,则执行堆栈展开:
    • 释放函数所占的内存空间
    • 对于类类型的局部对象调用她们的析钩函数(所以析钩函数应该不抛出任何异常)来清理对象
  3. 否则执行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;
}
  • 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;
}


昵    称:
验证码:

相关文档: