std::future 仅通过异常传递错误,不支持 errno 或 error_code 等错误码;正确做法是在异步任务中 throw 异常,由 get() 自动 re-throw;若需错误码语义,应封装为 variant 或结果类型,但会失去异常自动传播优势。

std::future 本身不传递错误码,它只传递异常或值
直接回答核心问题:std::future 的设计哲学是“成功返回值,失败抛异常”,它没有内置机制承载 errno、std::error_code 或自定义错误码整数。如果你看到某处用 std::future<int></int> 返回 -1 表示失败,那只是业务约定,不是 std::future 提供的错误处理能力。
常见错误现象:
- 调用
get()后捕获不到std::system_error,但实际任务内部throw std::system_error(errc)却被静默吞掉(原因见下条) - 用
std::future<:error_code></:error_code>试图“统一错误通道”,结果发现成功路径也要构造一个 dummystd::error_code(),语义混乱
正确做法:在异步任务中显式 throw 异常,由 future 捕获并 re-throw
std::future::get() 会把异步任务中抛出的任何异常(包括 std::system_error、std::runtime_error,甚至 int)原样重新抛出。这是标准保证的行为,也是唯一推荐的错误传播路径。
实操建议:
- 不要在
std::async或线程函数里 return 错误码;改用throw std::system_error(errc, "desc"),其中errc可来自std::errc::invalid_argument等枚举 - 若必须兼容 C 风格错误码(比如调用
open()),立即封装为异常:if (fd == -1) { throw std::system_error(errno, std::generic_category(), "open failed"); } - 避免 throw
int或裸字符串——它们无法携带错误上下文,且catch(...)容易掩盖真正问题
std::error_code 能否和 future 一起用?可以,但要包装成值类型
如果你坚持用 std::error_code 做错误载体(例如对接已有 C++11 兼容库),不能直接让 std::future 返回它,而应使用 std::variant 或自定义结果类型:
例如定义 struct Result { std::optional<int> value; std::error_code ec; };</int>,然后用 std::future<result></result>。但这会失去 get() 自动抛异常的便利性,所有调用方都得手动检查 ec。
关键权衡点:
- 用
std::future<t></t>+ 异常 → 简洁、符合 STL 习惯、错误不可忽略 - 用
std::future<result></result>→ 显式、可控、适合嵌入式或禁用异常环境,但代码变冗长 - 二者不能混用:一旦你选择异常路径,就别再往
std::future里塞std::error_code作为“备用错误通道”
容易被忽略的陷阱:std::async 的延迟求值与异常延迟抛出
std::async(std::launch::deferred, ...) 创建的 std::future 不会在构造时执行任务,而是在首次调用 get() 或 wait() 时才运行。这意味着异常不会在线程启动时抛出,而是在你取结果那一刻才爆发——这容易导致错误定位困难,尤其在日志缺失或堆栈被截断时。
规避方式:
- 除非明确需要延迟执行,否则始终用
std::async(std::launch::async, ...)确保任务立即在线程中开始 - 在
get()外层加try/catch是必须的,但别只 catchstd::exception;考虑catch(const std::system_error& e) { e.code(); e.what(); }来提取原始错误码 - 注意
std::future析构时若未调用get()且任务已异常终止,会直接调用std::terminate()—— 这是最隐蔽的崩溃源之一
最常被跳过的细节是:异常对象在跨线程传递时会被拷贝,如果自定义异常类型有非平凡析构或依赖 TLS,可能引发未定义行为。用标准异常类最安全。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











