异常
大约 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.h
或except.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
关键字能指出某函数不会引发异常
- 虽然C++11抛弃了异常规范,但还支持另一种异常规范:
- 写法
- 例如:
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,另一种异常规范