异常是OO语言处理错误的方式,在C++中,鼓励使用异常。侯捷再书中谈起异常,“十年前撰写“未将异常考虑在内的”函数是为一种美好实践,而今我们致力于写出“异常安全码”。”可见异常安全的重要。
说起异常安全,首先就要是异常的出现是为弥补C语言缺陷。再者,将介绍异常的概念,异常安全的条件。
C语言处理错误的缺陷
- 程序意外终止
比如:内存申请错误,越界,除0错误,会直接终止程序
- 错误码难以解读
在出错后会返回一个数字(错误码)。此时会包含俩层含义:是错误信息?是结果?
错误码需要程序员查找相关库信息
出现错误直接终止程序是非常不允许的情况。
C++异常的引入
异常:当一个函数出现自己无法解决的错误时,可以抛出异常,让函数的直接或间接调用者处理这个问题。
处理异常的三个关键字:
throw:当问题出现时,要抛出异常,通过throw抛出
catch:用于捕获异常。可以有多个catch
try:try中的代码将被激活特定的异常,try后跟着一个或多个catch块。
try要和catch匹配使用。
catch块中的内容不一定会被执行,只有当异常抛出且被捕获时才会执行,否则不执行。
try {//保护块}catch (ExceptionName e1){//}catch (ExceptionName e2){//}
异常的抛出与匹配规则
- 异常是有抛出对象引发的,该对象类型决定调用哪个块的。比如:抛出int类型的异常,catch参数为int的来接收。实际上:抛出和捕获类型不一定要相同,这里可以抛出派生对象。
- 被选中的处理代码是调用链中与该对象类型匹配且离抛出异常位置最近的那一个。
- 异常抛出的对象后,会生成一个临时拷贝,传给catch
在实际运用中,抛异常抛出通常对象是一个类,包含错误信息和错误码。
由于私有成员在内外拿不到,故通过函数调用返回错误信息和错误码。
对于临时拷贝的类型是const 故函数要添加const,才可调用。
- catch(...)可以捕获任意类型的对象。
抛出的异常在没有继承情况下,要匹配相应的类型才能被捕获,会在catch中一直匹配,直到catch(...)处理任意类型。但是无法得到抛出的异常对象。
异常的重新抛出
异常安全
具有异常安全的函数会
- 不泄露任何资源。例如上述代码在抛出异常后,后续的delete不会被调用。
- 不允许数据败坏。异常抛出后,异常被捕获,导致栈帧的跳跃,关键信息没有被执行。
解决资源泄露是比较轻松的
确保析构,智能指针。
这里我们专注解决数据败坏的问题。
在构造函数中,最好不要抛异常,可能会导致没有完全构造
析构过程最好不好抛异常
在lock和unlock抛异常会导致死锁
异常安全函数有以下三个保证
- 基本承诺
如果异常被抛出,程序内任何事物都保持在有效状态,没有任何对象和数据结构被破坏,所有对象处于一种内部前后一致的状态。
- 强烈保证
如果函数调用成功,就完全成功。如果函数失败,程序就恢复到调用之前状态。
- 不抛掷保证
承诺异常绝不抛出,因为它们总能够完成它们原先承诺的功能。
在C++11中,如果一个函数明确的不抛异常的话,就用noexcept
thread() noexcept;
thread (thread&& x) noexcept;
异常优点
- 可以清晰展示错误信息
- 抛异常可以直接拿到错误信息,不需要重重返回。
- 第三方库的异常安全很规范
- 部分函数更好检查。如构造函数没有返回值。
缺点
执行的跳跃,乱流。追踪程序困难。
C++没有垃圾回收机制,异常任意导致内存泄漏
标准库的异常不完善。
异常是被鼓励使用。时间不断前进,我们与时俱进!
参考:
<<Effective c++>>