在(keng)神(te)奇(duo)的C++中, 本智障经常写出一些正常的代码导致我的bug编译不过去. 举个书上的栗子,计算两个数的调和平均数的定义为: 两个数字倒数的平均数, 表达式:
2.0 * x * y / (x + y)
这样的话如果xy互为相反数的情况下岂不是很尴尬?
abort()
对于这种问题,处理方式之一是如果检查到xy互为相反数则调用
abort()
函数(abort处于 cstdlib.h ).他会想标准错误流发送 abnormal program termination (程序异常终止),而且返回一个由时间决定的值,告诉操作系统处理失败..当然也可以用exit(),只不过不显示消息而已;
|
|
异常机制
对于异常的处理有三个部分:
- 引发异常(throw)
- 使用处理程序捕获异常(catch)
- 使用 try 块(try)
throw关键字表示引发异常,紧随其后的值(如字符串或对象)指出异常的特征;
catch关键字表示使用异常处理程序(exception handler)捕获异常,括号内的表示异常要处理的类型,花括号内的表示遇到异常所采取的措施,虽然catch长的像个自带定义的函数,然而他并不是.
try关键字表示其中的代码可能会出现异常,他后面一般跟着一个或多个catch.
如码所示:
现在假设异常被触发,hmean()引发异常,被引发的异常是常量字符串:”异常:这俩数有毛病”,于是throw终止函数hmean()的执行,沿着函数调用序列往后查找,发现hmean()函数是从main()中的try块中调用的,于是throw把控制权返回给main函数,程序将在main里寻找与引发的异常类型所匹配的异常类型处理程序(说白了就是找参数类型跟throw后面的类型一样的catch块),程序找到唯一匹配的参数为char* 的catch块:类似下面的
于是,程序吧字符串:”异常:这俩数有毛病”赋值给s,然后执行catch(const char* s)
内的代码.
如果函数引发了异常而没有try块或没有匹配的catch时程序将调用abort()函数
将对象作用异常类型
通常,引发异常的函数将传递一个对象,这就可以通过不同的异常类型来区分不同的函数在不同的情况下引发的异常,另外对象可以携带信息,同时catch块可以根据这些信息来决定采取什么样的措施.请查看具体代码
举个栗子:
异常规范和C++11
C++98新增了一种不受待见(最好别用这玩意)的异常规范(exception specification),他长这样:
throw()部分就是异常规范,他可能出现在函数原型和函数定义中,他可以包含类型列表.这玩意的作用之一是告诉用户可能需要使用try块(然而直接写注释更方便),另一作用是让编译器添加执行运行阶段的代码,使劲检测是否违反了异常.
C++11资呲一种特殊的异常规范,使用noexcept指出函数不会引发异常,不过对于这个还是存在争议的:
栈解退
其实一张图就可以解释栈解退不过我还是要哔(chao)哔(xi)两句
假设try块没有直接调用引发异常的函数,而是调用了对引发异常函数进行调用的函数,则程序将从引发异常的函数直接跳到包含try块的函数.
C++是如何处理函数的调用和返回的?
程序将调用函数的指令的地址(返回地址)放到栈中.当被调用的函数执行完毕之后程序将通过地址来确定从哪里开始继续执行.函数调用将函数参数也放到了栈中,他们被视为自由变量,如果被调用的函数又调用了另一个函数,那么后者的信息也会被添加到栈中,以此类推.当函数结束时,程序流程将跳到调用函数时储存的地址处(也就是返回到调用他的那个函数里),同时栈顶元素被释放,以此类推.并在结束时释放自由变量,如果自动变量是类对象,那么他的析构函数将被调用(如果有析构函数的话).
现在假设异常被触发(程序终止),则程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址才停止.随后控制权将转到块尾的catch里,而不是调用函数后面的第一条语句,这个个过程被称为栈解退.
然而栈解退有个和函数返回一样的特征. 对于栈中的自动类型对象,类的析构函数将被调用.不同的是,函数返回仅仅处理放在栈中的对象,而throw则是处理try块和throw之间整个函数调用序列放在栈中的对象.
如果没有栈解退这种特性,则引发异常后,对于中间函数调用放在栈中的对象,他们的析构函数不会被调用.
(现在上图: throw 与 return