std::expected提供更安全的错误处理方式,C++23中可用,适合处理预期错误,如除零或解析失败,而异常仍适用于真正异常情况,两者互补使用提升代码健壮性。

在C++中,处理函数可能的错误是每个开发者都必须面对的问题。传统的做法包括返回错误码、使用输出参数、或抛出异常。随着现代C++的发展,特别是C++17之后引入的std::variant和即将在C++23中标准化的std::expected,我们有了更清晰、更安全的方式来表达“成功值或错误”的语义。
传统方式的问题:错误码与异常
早期C++常采用返回错误码的方式:
// 返回 bool,结果通过引用传出bool divide(double a, double b, double& result) {
if (b == 0) return false;
result = a / b;
return true;
}
这种方式不够直观,调用者容易忽略返回值,且无法携带丰富的错误信息。
另一种方式是使用异常:
立即学习“C++免费学习笔记(深入)”;
double divide(double a, double b) {if (b == 0) throw std::invalid_argument("Division by zero");
return a / b;
}
异常虽能传递详细错误,但代价高,控制流不明显,且在性能敏感或禁用异常的场景(如嵌入式)不可用。
std::expected:明确的预期结果
std::expected
示例:
#include#include
std::expected
if (b == 0) {
return std::unexpected("Division by zero");
}
return a / b;
}
// 使用
auto result = divide(10, 0);
if (result.has_value()) {
std::cout } else {
std::cout }
优点:
- 类型安全:错误类型E可以是任意类型,如enum、string、自定义错误结构
- 无异常开销:不依赖异常机制,适合noexcept环境
- 强制检查:编译器可警告未检查has_value()
- 支持链式操作:提供and_then、or_else等函数式接口
何时使用异常,何时使用std::expected?
两者不是替代关系,而是互补:
- 使用异常处理真正“异常”的情况:如内存不足、文件未找到、网络断开等程序无法继续执行的错误
- 使用std::expected处理“预期中的错误”:如输入校验失败、除零、解析错误等业务逻辑中常见的可恢复错误
例如,一个JSON解析函数应返回std::expected
实际建议与最佳实践
- 优先考虑接口的语义清晰性。如果错误是正常流程的一部分,用std::expected
- 避免混合使用异常和错误码,会造成调用者困惑
- 若项目暂不支持C++23,可用第三方实现如tl::expected(来自TartanLlama库)
- 为错误类型定义清晰的枚举或结构体,避免使用裸字符串
- 结合模式匹配(C++23或if-consteval)简化处理逻辑
基本上就这些。std::expected让C++的错误处理更接近Rust的Result类型,提升了代码的可读性和安全性。结合异常的合理使用,可以构建既高效又健壮的系统。关键在于根据错误性质选择合适策略,而不是统一用某一种方式。











