I.10: 使用异常来表示执行所需任务的失败
原因
不应该忽略错误,因为这会使系统或计算处于未定义(或意外)的状态,这也是错误的主要来源。
示例
int printf(const char* ...); // 糟糕:当输出失败时返回负值template // 好的:当不能开始一个新线程时抛出system_errorexplicit thread(F&& f, Args&&... args);
Note
什么是错误?
一个错误意味着函数不能达到它所宣传的目的(包括建立后置条件),忽略错误的调用代码可能导致错误的结果或未定义的系统状态。例如,不能连接到远程服务器本身并不是一个错误:服务器可以出于各种原因拒绝连接,因此自然会返回调用者应该始终检查的结果;但是,如果连接失败被认为是错误,则失败应该引发异常。
例外
许多传统的接口函数(例如UNIX信号处理程序)使用错误代码(例如errno)来报告真正的状态,而不是错误。没有很好的替代方法来使用这样的函数,因此调用这些函数并不违反规则。
替代方法
如果您不能使用异常(例如,因为代码充满了旧式的raw指针使用,或者因为存在硬实时(hard-real-time)约束),请考虑使用返回一对值的方式:
int val;int error_code;tie(val, error_code) = do_something(); // 译注:tie是C++11的用法if (error_code) { // ... 处理错误或退出 ...}// ... 使用 val ...
不幸的是,这种风格导致了未初始化的变量(译注:int val;会导致val没有被初始化),处理这个问题的工具结构化绑定将在C++17中提供(译注:C++17已经支持)。
auto [val, error_code] = do_something();if (error_code) { // ... 处理错误或退出 ...}// ... 使用 val ...
Note
我们不认为“性能”是不使用异常的正当理由:
- 通常,显式的错误检查和处理也会消耗与异常处理同样多的时间和空间。
- 通常,干净的代码在有异常的情况下会产生更好的性能(简化和优化程序中路径的跟踪)。
- 性能关键代码的一个好规则是将检查移到代码的关键部分之外(checking)。
- 从长远来看,更规范的代码会得到更好的优化。
- 在做出性能声明之前一定要仔细度量。
另请参阅: I.5 和 I.7 来报告违反先决条件和后置条件的情况
实施
- (不可强制执行的) 这只是一个哲学意义上的指导,不具直接检查的可行性。
- 寻找 errno。