错误码适用于系统级、性能敏感或无法抛异常的场景(如C接口、中断处理、嵌入式裸机);异常适用于现代C++应用逻辑,更安全、易组合、能准确表达“真正意外”。
错误码适合系统级、性能敏感或无法抛异常的上下文(如 C 接口、中断处理、嵌入式裸机);异常适合绝大多数现代 C++ 应用逻辑,它更安全、更易组合、更能表达“真正意外”的语义——这正是 Herb Sutter 在
GotW #91
和 C++ 委员会讨论中反复强调的立场。
什么时候必须用 error code?
当调用栈中存在无法捕获异常的边界时,
或整型错误码是唯一选择:
C ABI 兼容接口(如导出给 Python 或 Rust 调用的函数),
函数不能抛异常
信号处理函数(
handler)、中断服务例程(ISR),异常机制在这些上下文中未定义行为
内存极度受限环境(如某些嵌入式 RTOS),禁用异常运行时(
)后,
和
(C++23)成为替代方案
需要精确控制错误传播路径(例如:逐层检查并记录每个子操作的
),而异常会跳过中间栈帧
为什么异常在应用层更可靠?
Herb Sutter 指出,用错误码“手动传播”极易遗漏检查,而异常强制你面对错误——不是靠约定,而是靠语言机制:
忘记检查
是静默 bug;未捕获异常则程序终止(可配
),问题立刻暴露
异常支持资源自动释放(RAII),即使多层嵌套调用也能保证
、锁、文件句柄被析构;错误码需手动写
或重复
,易出错
和
可跨线程传递错误上下文;错误码只能传数字,丢失类型、消息、堆栈线索
不是异常的“轻量替代”,而是协作桥梁
Herb 明确反对把
当作“不抛异常的异常”来滥用。它的设计目标是与系统 API 互操作,不是替代
:
立即学习
“
C++免费学习笔记(深入)
”;
本身不带堆栈、不支持动态多态;
才是它的异常封装,该抛时就该抛
标准库
I/O(如
)默认抛异常;但提供
+
模式供 error-code 用户切换——这是分层设计,不是二选一
混合使用时,推荐:底层系统调用返回
,上层业务逻辑将其转为具名异常(如
)
真正难的是界定“意外”——网络超时是意外还是预期分支?Herb 的建议很实在:如果这个错误在绝大多数调用中都不该发生,且调用方无法合理恢复,就该用异常;如果它是常规控制流的一部分(比如“键不存在”之于哈希查找),
或
更贴切。别让错误处理逻辑模糊了业务意图。
error_codeextern "C"signal-fno-exceptionsstd::error_codestd::expectederrnoif (fd == -1)std::set_terminatestd::unique_ptrgoto cleanupifstd::exception_ptrstd::nested_exceptionstd::error_codestd::error_code&throwstd::error_codestd::system_errorstd::ifstream::open()std::ios_base::failbitclear()error_codethrow std::filesystem::filesystem_error(...)
// 正确协作:将 error_code 转为语义化异常
void open_config_file(const std::string& path) {
std::error_code ec;
std::ifstream f{path, std::ios::in | std::ios::binary};
if (!f.good()) {
std::filesystem::status(path, ec); // 系统调用返回 ec
if (ec) {
throw std::filesystem::filesystem_error(
"cannot open config", path, ec
);
}
throw std::runtime_error("config file empty or invalid");
}
}
std::optionalstd::expected