错误码和异常处理
程序出错几乎是必然事件,有可能是由于上游任务提供的数据就是错的。也有可能是程序内部实现,出现了bug。
对于程序出错的这种情况,需要有一套机制能够帮忙定位是什么错误,以及出现了错误之后如何处理的。
一般而言错误处理方式有两种处理方法
- 错误码
- 异常机制
1. 错误码
简单来说,网络请求返回的相应信息中的状态码其实也是一种错误码(404
等);宽带拨号上网失败也会产生错误码;程序运行崩溃生成日志,日志里面可能也带着错误码信息。
常见的错误码的设计,一般而言有这些:
- 错误码分模块处理:不同模块的错误码不是共同一个值域。
- 错误码分正负处理:正值为上游服务出问题;负值为程序内部实现出问题;零值表示没问题。
2. 异常
2.1 抛出异常,没有捕获会怎么样?
如果在C++程序中抛出了异常,但没有被捕获,那么程序会调用函数 std::terminate
,然后立即终止.这通常会导致程序的非正常退出.
在调用 std::terminate
之前,C++ 还会尝试调用一个名为 std::unexpected
的函数.你可以通过 std::set_unexpected
函数来设置 std::unexpected
的行为.如果 std::unexpected
函数没有调用 std::terminate
并且能够处理异常,那么程序可能会继续执行.但是,通常情况下,std::unexpected
函数会调用 std::terminate
.
std::terminate
函数的默认行为是调用 abort
来终止程序,但你可以通过 std::set_terminate
函数来改变这个行为.
总的来说,如果抛出了异常但没有被捕获,那么程序通常会立即终止.这就是为什么在编写可能会抛出异常的代码时,应该总是提供一个异常处理机制,以防止程序的非正常退出.
2.2 别让异常逃离析构函数
应避免让析构函数抛出异常.如果在析构过程中可能会产生异常,那么应该在析构函数内部捕获并处理这些异常,而不是让它们传播到析构函数的外部.
这里主要是因为如果循环析构10个Widgets,如果每一个Widgets都在析构的时候抛出异常,就会出现多个异常同时存在的情况,这里如果把每个异常控制在析构的话就可以解决这个问题.
因为C++标准规定程序必须立即调用 std::terminate 函数来终止程序.这是因为C++不允许在同一时间处理两个异常.
3. 总结
目前C++提供了统一的异常机制机制,但是没有提供统一的错误码机制.
3.1 c++为什么没有提供统一的错误码
-
灵活性:C++是一种通用的编程语言,用于各种不同的应用领域.在不同的应用领域中,可能需要处理的错误类型和数量都会有所不同.如果标准库提供了统一的错误码,可能无法满足所有应用领域的需求.
-
异常处理:C++提供了异常处理机制,这是一种更强大/更灵活的错误处理方式.通过抛出异常,可以提供比错误码更丰富的错误信息,而且可以处理更复杂的错误情况.
-
历史原因:C++的设计者选择了异常处理作为主要的错误处理机制,而不是错误码.这是一个设计决策,可能受到了历史原因的影响.
虽然C++标准库没有提供统一的错误码,但是它提供了一些类和函数来处理错误.例如,std::error_code
和std::error_condition
类可以用来表示和处理错误,std::system_error
异常可以用来抛出包含错误码的异常.此外,许多C++标准库函数(如文件I/O函数)会设置errno
变量来表示错误,这可以看作是一种错误码机制.
3.2 异常处理机制和错误码机制都是处理错误的方式,它们各有优点和适用场景.
异常处理机制更适合以下情况:
-
错误无法预见或无法立即处理:例如,内存分配失败/无效的类型转换等情况,这些错误无法预见,也无法立即处理,使用异常可以将错误处理逻辑和正常逻辑分离,使代码更清晰.
-
错误需要跨越多个调用层次:如果错误需要跨越多个调用层次传播,使用异常可以简化代码,避免在每一层都检查和传递错误码.
-
面向对象编程:在面向对象编程中,构造函数不能返回错误码,因此使用异常是处理构造函数错误的唯一方式.
错误码机制更适合以下情况:
-
错误可以预见并立即处理:例如,打开文件失败/找不到指定的元素等情况,这些错误可以预见,也可以立即处理,使用错误码可以让调用者直接知道函数是否成功.
-
性能敏感的代码:异常处理可能会对性能产生影响,尤其是当异常被频繁抛出时.对于性能敏感的代码,使用错误码可以避免这种性能损失.
-
与C语言代码交互:C++的异常不能跨越C++和C语言的边界.如果你的C++代码需要与C语言代码交互,使用错误码是更好的选择.
总的来说,选择使用异常处理还是错误码,取决于具体的应用场景和需求.