错误和异常处理(现代C++)
在 c 现代 + +,在大多数情况下,报告和处理的逻辑错误和运行时错误的首选的方法是使用异常。 在堆栈中可能包含检测到错误的函数的函数的已知道如何处理它的上下文之间的多个函数调用时更是如此。 异常提供正式的、 明确的方式将信息调用堆栈向上传递错误的代码。
程序错误通常分为两类: 通过编程错误,例如,"索引超出范围"错误,运行时错误的程序员来说,例如,不能控制的"网络服务不可用"错误所导致的逻辑错误。 在 C 样式编程和 COM 中,通过返回一个值,表示错误代码或为特定的功能状态代码或通过设置全局变量调用方可能会有选择地检索后是否报告错误,请参阅每个函数调用的错误报告管理。 例如,COM 编程使用 HRESULT 返回值传递给调用方,错误信息和 Win32 API 具有时函数检索报告了调用堆栈的最后一个错误。 在这两种情况下,由调用方来识别代码,并适当地对其做出响应。 如果调用方不会显式处理的错误代码,该程序可能会崩溃而不发出警告,或继续执行用错误的数据,并产生错误的结果。
例外情况被首选现代的 C++ 中,原因如下:
异常强制识别出现错误并处理它的调用代码。 未处理的异常停止程序执行。
异常将跳转到可以处理该错误的调用堆栈中的点。 中间的功能可以让例外传播。 他们不必非要与其它图层进行协调。
异常堆栈回退机制将销毁明确定义的规则的范围内的所有对象后,将引发异常。
异常使之间检测到错误的代码和处理该错误的代码完全分隔出来。
下面的简化的示例演示引发和捕获异常,C++ 中的所需语法。
#include <stdexcept>
#include <limits>
#include <iostream>
using namespace std;
class MyClass
{
public:
void MyFunc(char c)
{
if(c < numeric_limits<char>::max())
throw invalid_argument("MyFunc argument too large.");
//...
}
};
int main()
{
try
{
MyFunc(256); //oops!
}
catch(invalid_argument& e)
{
cerr << e.what() << endl;
return -1;
}
//...
return 0;
}
在 C++ 异常类似语言 (如 C# 和 Java。 在try块中,如果一个例外是, 引发将捕捉到的第一个相关联catch块的异常的类型相。 换句话说,执行会从throw语句以catch语句。 如果未不找到任何可用的 catch 块,则std::terminate调用且程序退出。 在 C++ 中,可能会引发任何类型。 但是,我们建议抛出一个类型派生自的直接或间接地std::exception。 在上一示例中,该异常类型, invalid_argument,标准库中定义 <stdexcept> 头文件。 C + + 不提供,并不需要, finally块,以确保如果引发异常释放所有资源。 资源这一收购是初始化 (RAII) 的用法,使用智能指针,它提供了所需的功能,对于资源清理。 有关更多信息,请参见 如何:异常安全模型。 有关 C++ 堆栈回退机制的信息,请参阅展开C++中的异常和堆栈。
基本原则
可靠的错误处理是一个挑战任何编程语言。 虽然异常提供多种功能,可支持正确的错误处理,但它们不能为您做的所有工作。 要实现的异常机制的好处,请记住异常设计您的代码。
使用断言来检查应永远不会发生的错误。 使用参数的公共函数的输入验证中的错误可能发生,例如,错误检查的异常。 有关更多信息,请参见 Exceptions VS. Assertions。
处理该错误的代码会检测到错误的代码可能分隔一个或多个插入的函数调用时,请使用异常。 应考虑是否使用错误代码而是在性能关键的循环中处理该错误的代码紧密耦合到检测到它的代码时。 有关何时不使用异常的详细信息,请参阅When Not to Use Exceptions。
对于每个函数,可能会引发或传播异常,提供的三种异常保证之一: 强的保证、 基本的保证或 nothrow (noexcept) 保证。 有关更多信息,请参见 如何:异常安全模型。
按值会引发异常,捕获通过引用它们。 不捕获,不能处理。 有关更多信息,请参见 引发和捕获异常的 (C++) 准则。
不要使用异常规范,不建议使用 C++ 11。 有关更多信息,请参见 Exception specifications and noexcept。
他们应用时,请使用标准库的异常类型。 派生自定义异常类型,从异常类层次结构。 有关更多信息,请参见 如何:使用标准库异常对象。
不允许像析构函数或内存解除分配函数的例外。
例外情况和性能
异常机制具有少量性能成本如果不会引发异常。 如果引发异常,堆栈遍历的成本和展开相当大致成本的函数调用。 其他数据结构所需跟踪后的调用堆栈的try块未输入,并且如果引发的异常,请展开堆栈所需的其他说明。 但是,在大多数情况下,并不重要的性能和内存占用量的成本。 异常对性能的负面影响可能会非常大,只能在非常有限的内存的系统上或者在性能关键循环错误很可能经常发生,并报告它的代码紧密关联的代码来处理它。 在任何情况下,它是很难知道异常的实际成本,而无需配置信息提取和测量。 即使是在极少数情况下时的成本是很重要,您可以权衡其对增加的正确性、 更方便的可维护性,并提供设计完善的例外策略的其他优点。
异常 VS.断言
异常和断言是两种不同的机制来检测在程序中的运行时错误。 使用断言测试条件不应为 true,则您的代码是正确的开发过程中。 在处理这样的错误使用异常错误表明在代码中必须固定的因为没有点并不表示该程序已在运行时从恢复的条件。 断言停止执行的语句处,以便您可以检查程序状态 ; 在调试器中 异常将继续从第一个适当的 catch 处理程序的执行。 使用异常检查错误情况可能发生在运行时,即使您的代码是正确的例如,"未找到文件"或"出的内存。您可能需要恢复这些条件,即使恢复只需将输出消息记录到日志,然后退出该程序。 通过使用异常检查公共函数的参数。 即使您的函数不包含错误,您可能没有完全控制权的用户可能会传递给它的参数。
C + + 异常而不是 Windows SEH 异常
C 和 C++ 程序可以使用结构化的异常处理 (SEH) 机制在 Windows 操作系统中。 SEH 中的概念类似于 C++ 异常中的不同之处在于使用 SEH __try, __except,和__finally构造而不是try和catch。 在 Visual C++ 中,C++ 异常 SEH 的实现。 但是,在编写 C++ 代码时,使用 C++ 异常语法。
有关 SEH 的更多信息,请参见 结构化异常处理(C++)。
异常规范和 noexcept
在 C++ 中引入的异常规范是作为一种方法指定的函数可能引发的异常。 异常规范证明有问题,在实践中,但不建议使用 C++ 标准草案 11。 我们建议您不要使用除异常规范throw(),指示异常允许无例外进行转义。 如果必须使用类型的异常规范throw(类型),注意, Visual C++ departs 标准以某些方式。 有关更多信息,请参见 异常规范。 noexcept说明符引入 C++ 的首选替代为 11 throw()。