如果发现异常情况
Should exceptions ever be caught
毫无疑问,异常是有用的,因为它们向程序员展示了他错误地使用函数或环境发生了不好的事情,但确实需要抓住它们吗?
未捕获的异常正在终止程序,但您仍然可以看到问题所在。在设计良好的库中,每个"意外"情况实际上都有解决方法。例如,使用map::find
而不是map::at
,在使用索引运算符之前检查int变量是否小于vector::size
。
为什么有人需要这样做(不包括使用强制执行它的库的人)?基本上,如果您正在为给定的异常编写处理程序,那么您还可以编写一段代码来防止它发生。
并非所有异常都是致命的。它们可能是不寻常的,因此也可能是"异常",但可以实现调用堆栈中更高的点来重试或继续。通过这种方式,异常用于将堆栈和嵌套的一系列函数或方法调用展开到程序中可以实际处理异常原因的点,即使只是清理一些资源、记录错误并像以前一样继续。
您不能总是编写防止异常的代码。举个明显的例子,考虑并发代码。假设我尝试验证i
在0和20之间,然后使用i
对某个数组进行索引。因此,我检查和i == 12
,然后使用它对数组进行索引。不幸的是,在测试和索引操作之间,其他一些线程向i
添加了20,所以当它被用作索引时,它已经不在范围内了。
并发性导致了竞争条件,因此针对异常条件进行保证的尝试失败了。虽然可以通过(例如)将每个这样的测试/使用序列包装在一个关键部分(或类似部分)来防止这种情况,但这样做通常是不切实际的——首先,获得正确的代码通常非常困难,其次,即使你确实获得了正确的代码,对执行速度的影响也可能是不可接受的。
异常还将检测到异常条件的代码与对该异常条件做出反应的代码解耦。这就是为什么异常处理在库编写器中如此流行的原因。库中的代码不知道如何正确应对特定的异常情况。对于一个非常琐碎的例子,我们假设它不能从文件中读取。它应该向stderr
打印消息、弹出MessageBox
还是写入日志?
事实上,它不应该做这些。对于任何给定的程序,至少有两个(可能全部三个)是错误的。因此,它应该抛出一个异常,并让更高级别的代码确定适当的响应方式。对于一个程序来说,记录错误并继续其他工作可能是有意义的,但对于另一个程序,文件可能足够关键,其唯一合理的反应是完全中止执行。
异常非常昂贵,性能也很差-因此,无论何时性能重要,您都希望编写无异常代码(使用"纯C"技术进行错误传播)。
然而,如果性能不是直接的问题,那么异常将允许您开发一个不那么混乱的代码,因为错误处理可以推迟(但随后您将不得不处理非本地控制权转移,这本身可能会令人困惑)。
我使用了大量异常作为一种方法,根据事件处理转移对特定位置的控制。例外情况也可能是将控制权转移到调用函数树中的"标记"位置的方法。当异常发生时,代码可能被认为是一次回溯一个级别,并检查该级别是否有异常活动并执行它
例外情况的真正问题是,你不知道这些情况会在哪里发生。到达异常的代码通常不知道为什么会出现问题,因此快速返回到已知状态是一个很好的操作。举个例子:你在威尼斯,看着地图穿过小路,突然你到达了地图上找不到的地方。从本质上讲,你很困惑,不明白自己在哪里。如果你有ariadne"μιτις",你可以回到一个已知的点,重新开始,试图到达你想要的地方。
我认为您应该将错误处理仅视为一种控制结构,允许返回到任何级别(通过错误处理例程和错误代码)。
- C++异常被捕获延迟,可能导致这种情况的原因是什么?
- 如何在编译器方便的情况下在 C/C++ 中发布算术溢出异常
- 如果从在其他函数中调用的函数引发异常会发生什么情况
- 如何避免字符串到整数转换情况下的无效参数异常
- 在存在异常的情况下使用for_each? std::exception_list
- 通过 std::async 启动的函数引发的异常会发生什么情况
- C++:在没有活动异常(GCC)的情况下终止调用
- 在scoped_ptr发生异常的情况下未调用析构函数
- 如果发现异常情况
- C++ 异常情况下的错误 C2228(".val"的左侧必须具有类/结构/联合)
- C++线程:在没有活动异常的情况下终止调用
- C++如何对开关情况进行异常处理
- 'new'语句是否可以在不引发异常的情况下失败?
- 如何在不调试符号的情况下确定从外部库引发的异常
- C++:检查是否在没有外部库的情况下抛出了某个异常类型
- 在什么情况下,if语句可以抛出异常
- catch语句如何在没有反射的情况下识别异常类型
- QT/C++类型的异常过载功能情况
- 在for循环内部使用时,是否存在任何异常甚至极端的情况,即后增量实际上比预增量更好
- 应用程序在未附加调试器的情况下引发异常