8/6 15.3 异常

Posted by 橙叶 on Wed, Aug 7, 2019

准备换成《C++ Primer》了...

15.3 异常

许多错误是在程序运行时发生的,要想程序更扎实,需要靠靠处理异常。

15.3.1 调用abort()

使用abort()中断程序。

abort()的原型位于cstdlib(或cstdlib.h)、命名空间std中。

15.3.2 返回错误码

函数在执行可能出现问题的语句前进行检查,如果参数不在可接受的范围内,就返回一个值(比如false)来告知调用方。

本质上是执行前进行检查。最终目的是告诉调用方这件事函数无法完成。

15.3.3 异常机制 try/catch

与执行前检查不同,使用try/catch的前提是程序已经执行,异常已经产生,但是可以对错误进行处理。

try{...}中包含可能出现问题的语句。

catch(...){...}对错误进行处理。括号中是异常类型。catch块可以有多个,匹配不同的异常对象,如果没有任何一个相匹配,则中断(abort())。

throw 用于产生一个异常。throw后面跟的对象由catch来接收。这个对象不限制类型(插一句,Python现在只能用Exception或继承Exception)。

15.3.4 将对象用作异常类型

throw的异常类型可以是对象。

15.3.5 异常规范和C++11

异常规范指在声明函数是表明这个函数是否会产生错误。如下:

double harm(double a) throw(bat_thing);

throw(...)的括号中包含了函数可能会出现的错误,置空(C++11中可使用noexcept)则示意不会有错误发生。一方面起到注释的作用,告知程序员是否应当使用try/catch,另一方面供编译器针对性地优化代码。

如书中所说,这其实是一个鸡肋功能,按理说,函数会不会异常、出现什么样的异常并不是能够完全确定的。我认为至少提醒作用交给注释和文档比较好。

15.3.6 栈解退

形象的说法是“错误上浮”。简单的说,函数中的一处语句产生异常,会立刻终止继续执行,在调用函数的地方产生这个异常,以此类推直至遇到try或回到main()。

注意一点,出现异常时程序是直接中断的,不会对对象调用析构函数,异常会一直上浮直到得到处理(被catch捕获或回到main())。函数因异常而中断时,会自动为栈中的自动类对象调用析构函数。

基本流程就是这样:

  1. 函数中某个语句抛出错误(对象);
  2. 函数中的自动变量被释放,如果变量是对象,那么将为对象调用析构函数;
  3. 异常上浮,到达调用抛出异常的函数的地方,然后继续上浮(也可以理解为在调用处在此抛出异常,接力下去);
  4. 异常上浮至main()或try块中,沿途所有函数的自动变量被释放;
  5. 异常对象传入catch块进行处理,若达到main()还没有try进行处理,程序终止。

15.3.7 其它异常特性

在引发异常时编译器会创建临时拷贝

当异常被抛出,栈解退,函数和自动变量从栈中释放,throw出的对象一样会消失,而编译器会创建一个异常对象的副本以供继续使用。

catch(...)仍然使用引用,是为了可以用一个基类匹配多个子类。

比如:

...
catch(Exception & e){
   ...
}

Exception的子类都可以被捕获。

使用catch(...){}捕获所有异常
多个catch块按顺序捕获

15.3.8 exception类

exception类来自exception头文件,exception类可用作其它异常的基类。

exception有一个虚拟成员函数what(),可以从exception继承,然后重写what()。what()可以用来以字符串的形式描述异常。exception类接受一个string对象作为参数。

1. stdexcept异常类
logic_error:逻辑错误
  • domain_error
  • invalid_argument
  • length_error
  • out_of_bounds
runtime_error:运行期间发生的难以预计和防范的错误
  • range_error
  • overflow_error
  • underflow_error # 下溢错误,一般小于浮点数可以表示的最小值
2. bad_alloc异常和new

由于使用new导致的分配问题,可以让new依法bad_alloc异常。bad_alloc的声明位于头文件new,由exception派生。

在以前的标准中,new返回一个空指针。

3. 空指针和new

如果需要让new返回一个空指针而不是bad_alloc异常,可以使用以下方法:

int *pi = new (std::nothrow) int;

15.3.9 异常、类和继承

没什么需要提到的新内容,略过。

15.3.10 异常何时会迷失方向

  1. 未与异常规范相匹配,称为意外异常,程序调用unexpected()函数,unexpected()函数默认调用terminate(),terminate()默认调用abort(),可以使用set_unexpected(unexpected_hanlder f)(参数是函数名)修改unexpected()的默认调用;
  2. 异常未被捕获,此时由terminate()调用abort()函数终止程序,可以使用set_terminate()(参数是函数名)设置要调用函数来替换abort();

15.3.11 有关异常的注意事项

在抛出异常时,函数内的对象会执行析构函数,但是函数内使用new请求的内存不会被自动释放。



comments powered by Disqus