c++异常处理的实现

 

我们编写的程序一般要满足正确性、健壮性、易读性和可复用性、可扩展性。健壮性指程序既能处理正确流程的情况,也能处理非法的错误的异常情况,提示用户出现了什么问题,一般由异常处理实现。在编写小型的程序时,可以很容易发现程序的错误,异常提示显得不那么重要,但在大型的由多人共同完成的程序中往往难以发现出现了什么问题,因此异常提示和处理显得尤为重要。下面我们了解c++的异常处理的实现。

异常机制提供程序中错误检测和错误处理部分之间的通信。C++的异常处理中包括:

throw表达式:错误检测部分抛出一个异常,说明程序遇到了无法处理的问题

try块:错误处理部分使用它处理异常。try语句块以try关键字开始,并以一个或多个catch子句结束。在try块中执行的代码所抛出的异常,通常会被其中一个catch子句处理。

由标准库定义的一组异常类,用来在throwcatch之间传递有关的错误信息。exception头文件定义了最常见的异常类,它的类名是exceptionstdexcept头文件定义了几种常见的异常类,new头文件定义了bad_alloc异常类,提供因无法分配内存而由new抛出的异常,type_info头文件定义了bad_cast异常类。exceptionbad_allo bad_cast只定义了默认构造函数,其他的异常类型则只定义了一个使用string初始化的构造函数,用于提示错误信息,异常类型只定义了一个what操作,这个函数没有参数,返回const char*类型,为该一场提供文字信息。异常类的继承层次如下图所示:

我们来看一个例子

int triangle(int a, int b, int c)

{

       if(a<0 || b<0 || c<0 || a+b<=c || a+c<=b || b+c<=a)

              throw runtime_error("The lengths of three sides can't form triangle");

       return a + b + c;

}

 

int main()

{

       int total = 0;

       try

       {

              total = triangle(3,4,7);

       }

       catch(const runtime_error& e)

       {

              cout<<e.what()<<endl;

       }

       cout<<total<<endl;

       return 0;

}

函数int triangle(int a, int b, int c)求三角形三边之和,函数首先判断这三条边能否构成一个三角形,不能就抛出一个异常转入异常处理块中。

异常是通过抛出(throw)对象而引发的,该对象的类型决定应该激活哪个catch子句,被选中的代码是调用链中与该对象类型匹配且离抛出异常位置最近的的那个。异常以类似于将实参传递给函数的方式抛出和捕获。

执行throw的时候,不会执行跟在throw后面的语句,而是将控制从throw转到匹配的catch,该catch可以是同一函数中局部的catch,也可以在直接或间接调用发生异常的函数的另一个函数中。控制从一个地方转移到另一个地方,有两个重要含义:

(1)       沿着调用链的函数提早退出

(2)       一般而言,在处理异常的时候,抛出异常的块中的局部存储不存在了。

抛出异常时对象为指针解引用形式时,异常对象的类型与指针的静态类型相同,不论指针是否指向其派生类型。通常抛出指针是个坏主意,发生异常时抛出异常块的局部存储被释放了。

抛出异常的的时候,暂停当前函数的执行,开始查找匹配的catch子句,首先检查throw本身是否在try块内,如果是,检查与该try相关的catch子句,看是否其中之一与被抛出的对象匹配。如果找到匹配的catch,就处理异常,如果找不到,就退出当前函数,并且继续在调用函数中查找。这个过程称为“栈展开”

栈展开期间,提早退出包含throw的函数和相关的调用它的函数,每个函数中的局部对象都会撤销,如果是类类型的,就调用其析构函数。考虑这个问题,如果在调用析构函数时跑出了新的异常,编译器怎么办?是取代旧的异常还是忽略新的异常?当然,析构函数应保证不会抛出异常。

构造函数有可能会抛出异常,甚至发生在初始化表中。为了处理来自构造函数初始化表的异常,必须将构造函数初始化为函数测试块。可以使用函数测试块将一组catch子句与函数连成一个整体。

template<class T> Handle<T>::T(T* p)

try:ptr(p),use(new size_t(1))

{

}

catch(const bad_alloc& e)

{

handle_out_of_memory(e);

}

如果最终没有找到匹配的catch子句,则调用库函数terminate.

catch子句的匹配:比实参和形参的匹配规则更严格,大部分转换都不允许,出下面几个可能的区别之外,允许从非constconst的转换,允许从派生类型到基类类型的转换,将数组转化为指向数组类型的指针,将函数转化为指向函数类型的适当指针。

异常说明符:异常对象本身是抛出对象的副本,是否再次复制取决于异常说名符(其实就是catch形参)。基类的异常说明符可以捕获派生类的异常对象,而且,异常说明符的静态类型决定catch子句可以执行的动作。如果被抛出的对象类型是派生类型的,但由接受基类型的catch子句处理,那么catch不能使用派生类的特有的成员函数。

catch子句的顺序必须反映类型层次。

有可能单个catch不能完全处理一个异常,在进行了一些矫正行动后,catch可能确定该异常必须有函数调用链中更上层的函数处理,catch可以通过重新抛出将异常传递给函数调用链中更上层的函数。重新抛出是后面不跟类型或表达式的一个throw

throw

throw语句将重新抛出异常,他只能出现在catch或从catch调用的函数中。被抛出的异常是原来的异常对象,而不是catch形参。

捕获所有异常的catch子句为:

catch(…)

{

 

}

异常说明:跟在函数形参表之后,在关键字throw后跟着一个(或为空)由圆括号括住的异常类型列表。

void recoup(int) throw(runtime_error);

空说明列表指出函数不抛出任何异常。

void no_problem() throw();

如果一个函数没有指出异常说明,则该函数可以抛出任意类型的异常。

异常说明有用的一种情况是,如果函数可以保证不抛出任何异常。

派生类虚函数的异常说明符必须比对应基类虚函数的异常说明符同样严格,或者更受限。

可以在函数指针的定义中提供异常说明符,在用另一指针初始化带异常说明的函数指针,或者将后者赋值給函数地址的时候,两个指针的异常说明符不必相同,但是,源指针的异常说明符必须至少与目标指针的一样严格,甚至更严格。

void (*pf)(int) throw() = recoup;                //error

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值