异常

C语言处理错误的方式

  1. 终止程序, 如assert
  2. 返回错误码

C++异常

异常是一种处理错误的方式, 当一个函数发现自己无法处理的错误时就可以抛出异常, 让函数的直接或间接的调用者处理这个错误

  • throw关键字: 当问题出现时, 程序会抛出一个异常
  • catch关键字: 在想要处理问题的地方, 通过异常处理程序捕获异常. catch关键字用于捕获异常, 可以有多个catch进行捕获
  • try: try块中的代码标识将被激活的特定异常, 它后面通常跟着一个或者多个catch块

如果有一个块抛出一个异常, 捕获异常的方法会使用try和catch关键字. try块中放置可能抛出异常的代码, try中的代码被称为保护代码

// 例
try {
	// 保护的标识代码
}
catch(ExceptionName e1) {
	// catch 块
}
catch(ExceptionName e2) {
	// catch 块
}
catch(ExceptionName e3) {
	// catch 块
}

异常的使用

异常的抛出和捕获

异常的抛出和匹配原则:

  • 异常是通过抛出对象而引发的, 该对象的类型决定了应该激活哪个catch的处理代码
  • 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个
  • 抛出异常对象后, 会生成一个异常对象的拷贝, 因为抛出的异常对象可能是一个临时对象, 所以会生成一个拷贝对象, 这个拷贝的临时对象会在被catch以后销毁

在函数调用链中异常栈展开匹配原则

  • 首先检查throw本身是否在try内部, 如果是, 再查找匹配的catch语句. 如果有匹配的, 则调到catch的地方进行处理
  • 没有匹配的catch则退出当前函数栈, 继续调用函数的栈中进行查找匹配的catch
  • 如果到达main函数的栈, 依旧没有匹配的, 则终止程序

异常的重新抛出

有可能单个的catch不能完全处理一个异常, 在进行一些校正处理后, 希望再调用更外层的调用函数链函数处理, catch则可以通过重新抛出异常传递给更上层的函数进行处理

异常安全

  • 构造函数完成对象的构造和初始化, 最好不要在构造函数中抛出异常, 否则可能会导致对象不完整或没有完全初始化
  • 析构函数主要完成资源的清理, 最好不要在析构函数内抛出异常, 否则可能会导致资源泄漏

异常规范

  • 异常规范说明的目的是为了让函数使用者知道可能抛出的异常有那些. 可以在函数的后面接throw(类型), 列出这个函数可能抛出的所有异常类型
  • 函数的后面接throw(), 表示不抛异常
  • 若无异常接口声明, 则此函数可以抛出任何类型的异常
// 表示这个函数会抛出A/B/C/D中某种类型的异常
void fun() throw(A, B, C, D);
// 表示这个函数只会抛出bad_malloc的异常
void *operator new(std::size_t size) throw(std::bad_alloc);
// 表示这个函数不会抛出异常
void *operator new(std::size_t size, void *ptr) throw(); 

C++标准库异常

// 所有标准异常的父类
std::exception
// 可以通过new抛出
std::bad_alloc
// 可以通过dynamic_cast抛出
std::bad_cast
// 适合处理C++程序中无法预期的异常
std::bad_exception
// 可以通过typeid抛出
std::bad_typeid
// 理论上可以通过读取代码来检测到的异常
std::logic_error
// 当使用了一个无效的数字域时, 会抛出该异常
std::domain_error
// 当使用了与无效的参数时, 会抛出该异常
std::invalid_argument
// 当创建了太长的std::string时, 会抛出该异常
std::length_error
// 该异常可以通过方法抛出
std::out_of_range
// 理论上不可以通过读取代码来检测到的异常
std::runtime_error
// 当发生数学上溢时, 会抛出该异常
std::overflow_error
// 当尝试存储超范围的值时, 会抛出该异常
std::range_error
// 当发生数学下溢时, 会抛出该异常
std::underflow_error

异常的优缺点

异常的优点

  • 相比错误码的方式可以清晰准确的展示出错误的各种信息, 甚至包括堆栈调用的信息. 这样可以帮助更好的定位程序的bug
  • 很多的第三方库都包含异常
  • 很多测试框架都使用异常
  • 部分函数使用异常更好处理. 例如, 构造函数没有返回值, 不方便使用错误码的方式处理\

异常的缺点

  • 异常会导致程序的执行流乱跳, 非常的混乱. 这会导致跟踪调试时以及分析程序时, 比较困难
  • 异常会有一些性能的开销, 但这个影响可以忽略不计
  • C++标准库的异常体系定义的不好

 


我们的征途是星辰大海!