一、C++ 中的异常机制 (exceptions)
- 头文件
cstdlib
/stdlib.h
包含:std::abort()
可以向标准错误流cerr
发送消息abnormal program termination
, 然后终止程序.std::exit(...)
/exit(...)
刷新文件缓冲区而不显示消息.
- 常见异常机制:
try
表示注意该代码块引发的异常.throw
类似跳转, 常见的是throw "..."
.catch (type)
处理异常.- 进阶版本:
- 可以
throw
一个类对象 (直接构造函数形式throw classname(x, y, ...)
),catch
中调用对象接口函数. - 异常规范: 现在不常用. 剩下一个
noexcept
放在函数声明后面表示不会引发异常. - 栈解退 (unwinding the stack):
throw
完之后层层栈解退, 直到到达try
所在那层, 然后 “搜寻”catch
. 与此同时自动变量, 对象会相应析构.throw
语句将控制权向上返回给第一个满足如下条件的函数: 包含能够捕获相应异常的try-catch
组合.throw
自动生成副本, 但仍然建议在catch
中使用引用. 主要原因是可以执行派生类对象.catch
块的排列顺序应和类的派生顺序相反 (否则引用出问题).
- 可以
- 头文件
exception
包含exception
类, 用class classname: public std::exception
可以使用其中的what()
方法, 一般return
一个字符串. - 头文件
stdexcept
十分万能:logic_error
系列domain_error
: 函数定义域.invalid_argument
: 传递的字符串出错.length_error
: 空间不够 (e.g. 数组).out_of_bounds
: 索引无效.
runtime_error
系列range_error
: 除了下面两者的所有.overflow_error
: 超过变量类型最大范围.underflow_error
: 低于了浮点数最小表示值.
bad_alloc
由exception
公有派生.std::nowthrow
,std::nothrow
.
- 异常何时出现问题
- 未捕获异常
- 调用
terminate()
函数. (头文件exception
)terminate()
默认调用abort()
函数, 但可以通过set_terminate()
函数修改行为 (但必须包含一个需调用的函数).typedef void (*terminate_handler)(); terminate_handler set_terminate(terminate_handler f) throw(); terminate_handler set_terminate(terminate_handler f) noexcept; void terminate(); void terminate() noexcept;
- 调用
unexpected()
函数. 可用set_unexpected
修改 (默认调用terminate
).
- 调用
- 一些不易引起注意的事项
- 如果用了
new
进行内存分配, 过早的终止会导致相应的delete
语句不被执行, 导致内存泄漏.解决方案:
- 在
catch
语句中加入相应的delete
语句. - (后文会提到) 智能指针模板.
- 在
- 如果用了
- 未捕获异常
二、与异常机制相结合: 更安全的四种类型转换
- RTTI (Runtime Type Identification 运行阶段类型识别)
- 用途: 虚函数能够 “自动” 地知道某指针指向的对象类型, 并自动调用其方法, 而如果我们需要把这一功能 “解封装”, 那就要用到 RTTI.
- 实现
Class1ptr* c1 = dynamic_cast<Class2ptr>(var)
: 只会告诉你转换是否安全, 但不会告诉你具体是什么类型. 不安全返回空指针. 配合if
可以包含检验环节.另外几种常见的强制类型转换:
-
static_cast
: 几乎与dynamic_cast
相同, 但是没有错误检查机制. 同时dynamic_cast
能够检查形如 “A 同时派生了 B 和 C, B 与 C 之间的类型转换 (返回nullptr
)”, 但static_cast
不行. 但static_cast
还允许枚举类型和整型之间以及数值类型之间的转换。注: 这两种转换在开发人员进行安全保证的情况下还可以脱离类来转换 (
int -> char
, etc.). -
reinterpret_cast
: “近乎万能”, 不关心类的继承关系. -
const_cast
: 把常量指针 / 引用转换为非常量指针 / 引用 (好处是确保了大部分时候的安全性).
-
typeid
(用到头文件typeinfo
) 重载了运算符==
和!=
, 常见有:typeid(var1 / typename1) == typeid(var2 / typename2)
typeid(varname).name()
重要提示: 如果发现在扩展的
if else
语句中使用了typeid
, 则应考虑是否应该使用虚函数和dynamic_cast
.