跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

c++的错误码(error code)和异常(exception)该如何抉择? (Herb Sutter的观点)

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

相关文章