C++ 标准库中的异常是标准库的一个组成部分, 但异常并不是 STL 的一部分, 所以下面这些内容里面不会展现任何关于泛型乃至模板相关可能令人不适的内容.
那, 先在 C 身上找点错误处理相关的乐子.
使用返回码进行错误处理
上篇文章聊的是一个非常具体的异常处理, 而在这个环境中, 使用异常是不得已的事情. 在 C 语言单调的世界里, 异常还是不存在的, 大家都非常和谐地使用特别的返回值来标记错误发生 (比如 fgetc 返回 EOF 表示文件读完了), 甚至返回值就直接是错误码本身 (如 fseek).
错误码机制会严重破坏结构化程序设计, 虽然 C 对自己的定位正是结构化语言中的战斗机. 特别是, 如果一段代码中充满如堆内存申请, 文件 IO 这些容易发生错误的调用, C 代码的错误处理方式便从骨子里透露出一股原始浑厚的蛮荒气息, 比如下面这段
int encrypt(char const* src_file, char const* dst_file, char const* key)
{
int ret = 0;
void* buffer;
FILE* dstf;
FILE* srcf = fopen(src_file, "rb");
if (NULL == srcf) {
return 1;
}
dstf = fopen(dst_file, "wb");
if (NULL == dst_file) {
ret = 1;
goto EXIT_CLOSE_SRC;
}
buffer = malloc(BLOCK_SIZE);
if (NULL == buffer) {
ret = 1;
goto EXIT_CLOSE_DST;
}
while (0 == read_buffer(buffer, BLOCK_SIZE, srcf)) {
encrypt_block(buffer, key);
if (0 != write_buffer(buffer, BLOCK_SIZE, dstf)) {
ret = 1;
goto EXIT_FREE_BUFFER;
}
}
if (!feof(srcf)) {
ret = 1;
}
EXIT_FREE_BUFFER:
free(buffer);
EXIT_CLOSE_DST:
fclose(dstf);
EXIT_CLOSE_SRC:
fclose(srcf);
return ret;
}
这里还只显示了这个函数的情况, 而它的调用者可能要应对更多, 比如将无法处理的错误逐个栈帧返回, 最终程序流程会显得极其难看. 不过在现代技术的帮助下, 我们有很多方法能避免上面这种结构混乱化程序设计, 比如换成像 C++ 这样支持异常的语言.
C++ 异常 抛与接
异常的优势在于, 它彻底分离错误出现与错误处理代码, 至于能不能增强代码健壮性那是另一回事, 也许程序会漏捕某些类型的异常, 当然这属于软件 bug 的范畴, 这里就不说了.
异常还有一个特性是, 函数会抛出什么, 在代码中会清晰的写出来 (而不是像错误码那样写在尘封的文档里), 不过这一点 C++ 比较扯, 即使一个函数声明什么也不抛 (throw()), 也可以随便抛. 这在编译时并不检查, 但是在生成的执行代码中会体现, 比如下面这段代码
#include <iostream>
#include <string>
void func_throw_int(int i) throw (std::string)
{
throw i;
}
int main()
{
try {
func_throw_int(10);
} catch (int) {
std::cout << "function throws integer." << std::endl;
} catch (std::string) {
std::cout << "function throws string." << std::endl;
} catch (...) {
std::cout << "exception caught." << std::endl;
}
return 0;
}
最终的结果是, 谁也没能真正捕获到这个在栈帧中穿梭的整数, 程序直接崩溃. 如下代码也会这样
#include <iostream>
#include <string>
void func_throw_int(int i) throw (std::string)
{
throw i;
}
void func_wrapper(int i) throw (int, std::string)
{
func_throw_int(i);
}
int main()
{
try {
func_wrapper(10);
} catch (int) {
std::cout << "function throws int." << std::endl;
} catch (std::string) {
std::cout << "function throws string." << std::endl;
} catch (...) {
std::cout << "exception caught." << std::endl;
}
return 0;
}
可以看出, C++ 编译器还是会根据函数的异常描述选择性地在栈帧上布下眼线, 而不是粗放式地乱抓一气, 即使是 catch (...) 也不是银弹, 大家还是养成良好习惯, 不要乱声明乱抛乱抓了哦.
这部分的最后再介绍一个几乎不为人知的标准库函数, (我也不太清楚这个大隐隐于标准库的家伙到底是为什么被设计出来的)
typedef void (std::* unexpected_handler)();
std::unexpected_handler std::set_unexpected(std::unexpected_handler new_handler);
它接受一个函数指针作为参数, 返回之前值班的函数. 传入的这个函数执行的时机是, 当有某个出问题的函数中抛出的异常类型与函数实际声明的异常不一致, 这个函数的栈帧被弹出后的那个时刻. 另一方面, 由于 std::unexpected_handler 型函数不接受任何参数, 从而也就无法诊断任何跟误抛相关的信息, 还不如上篇文章谈到的简易 signal 呢. 下面这段例子演示它如何工作
#include <iostream>
#include <string>
#include <exception>
void unexpected_throw()
{
std::cout << "d'oh!" << std::endl;
}
void func_throw_int(int i) throw(std::string)
{
throw i;
}
int main()
{
std::set_unexpected(unexpected_throw);
func_throw_int(10);
return 0;
}
效率
选择 C++ 开发也许看中的就是 C++ 程序运行时高效, 所以错误处理的效率很自然地会在某个时刻 (很可能是解决掉内存分配器的效率问题之后 :) 被提上议程. 异常的效率很难衡量, 特别是如果需要与 C 的错误码机制的效率相提并论时, 两者几乎没什么可比性.
如果使用异常来处理错误, 一段平铺直叙的代码在没有发生错误的情况下会表现得非常好, 但是一旦发生错误, 异常处理程序会产生极大的开销; 而使用错误码加分支的方法, 在任何情况下表现都很平均, 而且分支预测成功率会显著地反映在错误处理代码中.
这里就不扯什么 "一般情况推荐用异常, 极端情况下使用错误码来提高平均效率" 之类的废话了, 总之, 这是个很有争议的问题, 纸上谈兵也没用, 这里就打住吧.
分享到:
相关推荐
4. **异常处理**:使用SpringBoot的异常处理机制,如`@ExceptionHandler`注解或全局异常处理器,捕获并转换异常为带有错误码的响应。 5. **统一的错误响应格式**:设计一个统一的JSON格式来封装错误信息,包括错误...
Linux 网络编程 socket 错误码分析 在 Linux 网络编程中,socket 函数可能会返回多种错误码,这些错误码提供了有价值的信息,可以帮助开发者诊断和处理网络编程中的问题。本文档总结了常见的 socket 错误码及其处理...
7. **实例与进程状态**:某些错误码与数据库实例或后台进程的状态有关,了解这些状态可以帮助理解错误的上下文。 8. **PL/SQL编程错误**:对于在PL/SQL代码中遇到的错误,手册会指导如何调试和修复代码。 9. **...
"Oracle错误码大全"就是这样一个宝贵的资源,它包含了6513个不同的错误码,几乎覆盖了Oracle数据库可能出现的所有异常情况。 首先,我们要理解Oracle错误码的基本结构。Oracle错误码通常由一个三位数字组成,如...
除了标准的错误码,Oracle数据库还有一系列与特定模块或特性相关的警告和异常,如PL/SQL错误、网络连接问题、性能优化难题等。"Oracle错误码大全"都会对这些情况进行详尽的解释。 总的来说,"Oracle错误码大全"是...
### DB2错误码详解 #### 一、概述 在数据库管理与维护过程中,遇到各种各样的错误是在所难免的。为了能够快速定位并解决问题,掌握常见的错误码及其含义至关重要。本文将详细介绍DB2中一系列常见错误码的具体含义...
例如,错误码10002表示服务器返回的链接关闭异常,可能由于服务器主动断开了连接;2002则表示参数格式错误,可能是客户端发送的数据不符合服务器要求的格式;2006表示会话过期,意味着用户需要重新登录;259是会话...
这些错误码是通过 errno 变量来表示的,当 Linux C API 函数发生异常时, errno 变量将被赋予一个整数值,该值对应不同的错误类型。 在 Linux 系统中,错误码被定义在多个头文件中,其中包括 errno-base.h 和 errno...
8. **错误码与异常** 在某些场景下,错误码(如返回值或全局变量)可能是更合适的错误处理方式,因为它不需要额外的运行时开销。然而,异常处理提供了更自然的错误流控制,并能更好地处理复杂的情况,如多个错误...
1. **调试阶段**:在开发过程中,当程序崩溃或抛出异常时,错误码可以帮助开发者定位问题的根源。 2. **故障排查**:对于运行中的系统出现的问题,可以通过错误码来快速识别问题,并采取相应的修复措施。 3. **...
Oracle数据库是全球广泛使用的大型关系型数据库管理系统,其在运行过程中可能会遇到各种错误,这些错误通常以错误码的形式出现,帮助管理员识别问题所在并解决。"Oracle错误码大全"是一份非常重要的参考资料,它汇集...
### DB2错误码详解 #### 引言 在数据库管理领域,DB2作为IBM的一款高性能关系型数据库管理系统,被广泛应用于各种规模的企业级环境中。在实际应用过程中,开发者和技术支持人员经常会遇到各种各样的错误码,这些...
普通的UML分析图,用来设计密码错误异常处理,应该还可以,希望收留
6. 日志和跟踪文件:Oracle数据库的alert.log和trace文件是诊断错误的重要资源,它们记录了数据库运行过程中的事件和异常,包括错误码的具体信息和堆栈跟踪,可以帮助深入理解问题的根源。 7. 优化和预防:理解并...
汇编语言,作为底层编程的一种,需要程序员直接与硬件交互,因此理解DOC错误码对于解决系统级问题至关重要。 首先,我们要知道DOC错误码通常指的是Microsoft Word文档格式在读取、写入或处理过程中出现的异常情况。...
正数错误码通常表示在执行SQL语句或与数据库交互时遇到的问题。下面逐一解释这些错误码及其含义: 1. **+01201545**: 表示数据库连接存在问题,可能是由于连接超时、网络问题或其他连接异常导致。 2. **+09801568**...
很全的IBM WebSphere MQ 错误码大全
### 海康威视SDK开发中的错误码解析 在进行海康威视SDK开发的过程中,开发者经常会遇到各种错误码,这些错误码可以帮助我们快速定位问题并进行相应的处理。本文将详细解析海康威视SDK中常见的一些错误码,并提供...
Informix错误码是系统在遇到问题时返回的一串数字,每个错误码都代表了一个特定的故障或异常情况。这些错误码通常包括三位数字,如999,其中第一位数字表示错误的严重程度,后两位数字则代表具体的错误类型。错误码...