C++的异常处理

1、抛出异常

(1)异常用类(class)来实现。

(2)当程序出现异常时,可以通过产生(raise)或抛出(throw)异常来通知“异常已经发生”。其中,抛出异常通过 “throw 表达式” 来实现。需要注意的是:表达式的类型是被抛出异常的类型,并且表达式里面的这个异常必须是一个对象。例如:

throw popOnEmpty;//这个异常并不是一个对象,所以不正确,正确的方式应该是:throw popOnEmpty();

(3)异常不仅可以是class类型的对象,也可以是任何类型的对象。

2、try块与捕获异常

(1)try的形式如下:

我们的程序中,应该将哪些代码放在try块中呢?答:首先是需要抛出异常的语句,其次应该把处理程序异常的代码与栈的正常操作的实现分离开。

对于类的构造函数,当需要使用try块来抛出异常时,不仅需要考虑构造函数的函数体,还需要考虑构造函数的初始化列表中如果有函数的调用(如果这个函数里面也有try块)它是否可能出现异常,因此try块的开始位置应该是初始化列表之前。

(2)try块引入一个局部域,在try块内声明的变量不能再try块外被引用。

(3)使用try块之后,程序的控制流程:

如果没有发生异常,则执行完try块的代码之后,当前函数调用结束。

如果抛出异常,当前函数后面的语句被忽略,try块被退出,然后程序执行权被转交给处理异常的catch子句。(注意:当异常被组织成类层次结构时,类类型的异常可能会被该类类型的共有基类的catch子句捕获到,所以catch子句的检查顺序是它们在try块后出现的顺序,派生类类型的catch子句必须先出现。)

如果异常声明的类型与被抛出的异常类型匹配,则选择这段处理代码来处理异常,其中当处理完之后,这个catch子句不包含返回语句,那么程序的执行将直接跳到所有的catch序列之后执行其他语句,原因:是C++的异常处理机制被称为是不可恢复的,一旦异常被处理,程序的执行就不能够在异常被抛出的地方继续执行。

如果没有找到 catch 子句, 则在主调函数中继续查找 ,如果一个函数调用在退出时带着一个被抛出的异常, 并且这个调用位于一个 try 块中 则检查与该 try 块相关联的 catch 子句, 看是否有一个子句能够处理该异常, 如果找到了一个 catch 子句 ,则该 异常被处理 ,如果没有找到 catch 子句, 则查找过程在主调函数中继续这个过程沿着嵌套 。函数调用链向上继续直到找到该异常的 catch 子句只要一遇到能够处理该异常的 catch 子 句,就会进入该 catch 子句 ,程序的执行在该处理代码中继续。(注意:在查找用来处理被抛出异常的catch子句时,因为异常而退出复核人语句和函数定义,这个过程被称为“栈展开”。除此之外,将资源的释放在析构函数中,随着栈展开,调用结束的函数和对象就会被释放,那么它们所获取的资源也会随着被释放。)

如果已经遍历完程序代码还是没有catch子句能够处理该异常,则程序执行权又被交给C++标准库定义的函数terminate(),此函数在缺省的情况下调用abort(),从而只是程序非正常退出。

(4)异常对象

catch子句的异常声明可以是一个类型声明或一个对象声明。当catch子句中的异常声明应该是一个对象的情况是:我们要获得throw表达式的值,或者要操纵throw表达式所创建的异常对象时,我们应该声明一个对象,假设我们设计自己的异常类,当该类被抛出时,我们把信息存储在异常对象中,如果catch子句的异常声明了一个对象,则catch子句中的语句就可以用该对象来引用由throw表达式存储的信息。

为什么要创建一个异常对象?原因是:在throw抛出的异常对象是临时的,随后就会被析构掉,那么将导致我们在捕获异常的时候没有办法得到之前抛出异常的信息。因此使用异常对象,将throw抛出的异常对象通过拷贝构造函数创建一个副本,然后把它放到异常对象的存储区中,它保证会持续到异常被处理完毕。

异常对象总是在抛出点通过调用构造函数被创建,直到该异常的最后一个catch子句退出时,它才被销毁。

catch子句括号内的异常声明类似于按值传递的参数,此时就会调用异常类型的拷贝构造函数,但是异常声明如果是按引用传递,那么catch子句能够修改异常对象。

catch子句的异常声明声明为引用的原因①确保应用在catch子句中的异常对象上的修改操作,能够反映到被重新排除的异常对象上。②确保能正确地调用与异常类型相关论地虚拟函数。(对于第二点,需要详细解释的是:如果被抛出的异常对象是派生类类型的,并且它被针对基类的catch子句处理,则catch子句一般不能使用派生类类型的特性。
原因是:
catch子句的异常声明的行为与参数声明十分相似,在进入catch子句时,因为异常声明在这里声明了一个对象eobj,所以obej以“异常对象的基类子对象的一个拷贝”作为初始值。
所以:为了调用派生类对象的虚拟函数,可以考虑多态的特性,将异常声明为一个指针或引用。)

(5)重新抛出

重新抛出:catch子句可能决定该异常必须由函数调用链中更上级的函数来出路,因此会把异常传递给函数调用链中更上一级的另一个catch子句。被重新抛出的异常就是原来的异常对象。

形式为:throw;

(6)catch(…)

表示捕获任意类型或对象声明

3、异常规范

异常规范用关键字 throw 来指定,后面是用括号括起来的异常类型表。表中指定被排除的异常类型,除了表中的异常类型之后不会再抛出其他类型。例如:extern int foo( int = 0 ) throw(string);

异常声明必须再头文件中的函数声明上指定,同一个函数的重复声明必须指定同一类型的异常规范。

如果函数抛出了, 一个没有被列在其异常规范中的异常, 并且该函数自己并没有处理,则系统调用 C++标准库中定义的函数 unexpected(), unexpected()的缺省行为是调用 terminate()。

在被抛出的异常类型与异常规范中指定的类型之间不允许类型转换。但是这个规则有一个例外:当异常规范指定一个类类型或类类型的指针时。如果一个一场规范指定了一个类,则该函数可以抛出“从该类公有派生的类类型”的异常对象。

也可以在“函数指针的声明处、类的成员函数处”给出一个异常规范。

异常规范跟在函数声明的const和volatile限定修饰之后。

当带有异常规范的函数指针被初始化或被赋值时,等号右边的指针必须比等号左边的指针一场规范更严格。例如:

注:throw()表示没有任何异常会被抛出。

基类中虚拟函数的一场规范可以与派生类改写的成员函数的异常规范不同。但是派生类虚拟函数的异常规范必须与基类虚拟函数的一场规范一样或者更严格。
原因:这可以确保当派生类的虚拟函数被通过基类类型的指针调用时,该调用保证不会违背基类成员函数的一场规范。

4、C++标准库的异常类层次结构

可以参考下面的文章:

C++标准库异常https://blog.csdn.net/linxi8693/article/details/90318166

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

仟各

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值