跳至主要內容

异常

LincZero大约 5 分钟

异常

异常处理方式

调用abort()exit()

  • abort()函数
  • exit()函数
    • 基本同上
  • abort()exit()区别
    • abort()
      • 是否刷新文件缓冲区取决于实现
      • 会向标准错误流
    • exit()
      • 会刷新文件缓冲区
      • 不会显示信息
  • 一些额外补充
    • 标准错误流:即cerr使用的错误流
    • 文件缓冲区:用于存储读写到文件中的数据的内存区域

返回错误码

通过函数的返回值来指出问题,该方式更灵活

异常机制(throw-catch

通过抛出异常

  • 使用

    • 举例

      try {
          throw "error1";  // 异常类型可以是字符串或其他C++类型,通常为类类型
      }
      catch (const char *s) {
          // ...
      }
      
  • 原理

    • throw语句导致程序沿函数调用序列后退,即栈解退,直到找到包含try块的函数
    • 栈解退unwinding the stack
      • 与函数调用的返回有点像但并不同
      • 返回值会返回到调用它的函数,这个过程会释放函数有关的栈(如自动变量),以此类推
      • 栈解退也释放栈,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址 随后,控制权将转到块尾的异常处理程序,而不是后面的第一条语句 如果没有找到这样的处理程序(try块),默认情况下程序将异常终止
    • 临时拷贝
      • 引发异常时总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用
      • catch块一般使用引用类型的原因并非提高效率(无法做到),而是让其可以执行派生类对象

异常类

传递对象好处

通常引发异常的函数会传递一个对象,优点:

  • 可以使用不同的异常类型区分不同的异常

  • 对象可以携带信息,程序员可以根据这些信息来判断异常原因

  • 对象可以携带信息,catch块可以根据这些信息来决定采取什么样的措施

    • 比如

      try {
          // ...
      }
      catch (Error1 &e){
          // ...
      }
      catch (Error2 &e){
          // ...
      }
      

exception类(exception头文件)

头文件exception(异常类)

头文件exception/exception.hexcept.h,定义了exception

C++可以把它用作其他异常类的基类,C++库也定义了很多基于exception的异常类型(派生类)

头文件stdexcept(标准异常类)

头文件stdexcept定义了其他几个异常类

  • logic_error,逻辑错误,描述典型的逻辑错误 (基于exception类的派生类,其又派生出许多其他类)
    • domain_error,定义域错误,可以让函数在参数不再定义域之间是返回domain_error异常
    • invalid_argument,无效的参数,可以指出给函数传递了一个意料外的值
    • length_error,空间不足,可以指出没有足够的空间来执行所需的操作
    • out_of_bounds,索引错误,可以用于指示索引错误
  • runtime_error,运行阶段错误,通常是可能在运行期间发生但难以预计和防范的错误 (基于exception类的派生类,其又派生出许多其他类)
    • range_error,非上下的溢出错误
    • overflow_error,上溢出错误
    • underflow_error,下溢出错误

bad_alloc异常和头文件new

对于使用new导致的内存分配问题,C++最新处理方式是让new引发bad_alloc异常

头文件new包含bad_alloc类的声明(定位new也是这个头文件),该异常类也是从exception类公有派生而来的

空指针和new

很多代码都是在new在失败时返回空指针时编写的,C++标准也提供了一种在失败时返回空指针的new

  • 使用:
    • 例如int *pi = new (std::nothrow) int;
    • 例如int *pa = new (std::nothrow) int[500];

异常规范和C++11

  • 简概
    • 异常规范exception specification)是C++98新增的一项功能,但C++11摒弃了
    • 异常规范有些缺点,比如函数引发了异常规范中没有的异常时,其机制会导致处理起来比较麻烦
  • 作用
    • 告诉用户可能需要使用try块。然而这项工作也可以使用注释轻松完成
    • 让编译器添加执行运行阶段检查的代码,检查是否违反了异常规则。但这很难检查得到
  • 使用
    • 例如double harm(double a) throw(bad_thing);,表示可能会抛出bad_thing类型的错误
    • 例如double marm(double) throw();,表示不会抛出错误

noexcept

  • 作用
    • 虽然C++11抛弃了异常规范,但还支持另一种异常规范:noexcept关键字能指出某函数不会引发异常
  • 写法
    • 例如:double marm() noexcept;

意外异常、未捕获异常、默认行为

简概

异常被引发后,在两种情况下回引发问题

  • 在带异常规范的函数中引发,则必须与规范列表中的某种异常匹配,否则称为意外异常
  • 如果异常不是在函数中引发(或则没有异常规范),则必须捕获它,否则该异常被称为未捕获异常

未捕获异常

未捕获异常会调用函数terminate(),默认情况下,terminate()调用abort()导致程序异常终止

但可以用set_terminate()修改terminate()的默认行为(这两个函数都在头文件exception中被声明)

  • 方法

    •   typedef void (*terminate_handler)();
        terminate_handler set_terminate(terminate_handler f) throw(); 		// C++98,异常规范语法
        terminate_handler set_terminate(terminate_handler f) noexcept; 		// C++11,另一种异常规范
        void terminate();													// C++98
        void terminate() noexcept;											// C++11,另一种异常规范
      

意外异常

情况与未捕获异常相似,会先调unexpected()函数,默认行为为程序异常终止

但可以用set_unexpected()修改unexpected()的默认行为(这两个函数都在头文件exception中被声明)

  • 方法

    •   typedef void (*unexpected_handler)();
        unexpected_handler set_unexpected(unexpected_handler f) throw(); 	// C++98,异常规范语法
        unexpected_handler set_unexpected(unexpected_handler f) noexcept; 	// C++11,另一种异常规范
        void unexpected();													// C++98
        void unexpected() noexcept;											// C++11,另一种异常规范
      

功能扩展

异常 x new类成员