std::promise 和 std::future 不可复制、仅可移动,且移动后原 promise 失效;必须显式 std::move 传递,set_value 必须被调用(含异常路径)才能使 future 就绪;std::async 默认非严格异步,多消费者需 shared_future;链式调用需自行封装状态机。

std::future 和 std::promise 不能跨线程移动,必须用 std::move 传递
直接把 std::promise 对象传给新线程(比如 lambda 捕获值拷贝)会导致运行时崩溃或未定义行为——因为 std::promise 是不可复制的,且其内部状态绑定到创建它的线程。常见错误是写成:
std::thread t([p = promise] { p.set_value(42); });这会触发编译错误(std::promise 没有拷贝构造函数)。正确做法是显式 std::move,并在 lambda 中按值捕获移动后的对象:
- 用
std::thread t([p = std::move(promise)] { p.set_value(42); }); - 若在线程外还需访问
std::future,务必在 move promise 前调用promise.get_future() - 一旦
std::promise被 move,原对象处于有效但未就绪状态,不能再调用set_value等方法
std::future::wait_for() 返回 ready 的前提是 set_value 已被调用,而非线程结束
很多人误以为只要子线程执行完,std::future 就 ready;其实它只关心 std::promise::set_value(或 set_exception)是否被调用。线程可能已退出,但忘了调用 set_value,此时 wait_for 会超时返回 std::future_status::timeout。
- 务必确保每个代码路径(包括异常分支)都调用
set_value或set_exception - 推荐用 RAII 包装:在 lambda 入口声明一个
auto cleanup = [&p]() noexcept { try { p.set_value(); } catch(...) { p.set_exception(std::current_exception()); } };,再配合std::unique_ptr或局部作用域保证调用 -
wait_for(std::chrono::seconds(1))返回std::future_status::ready才能安全调用get(),否则get()会阻塞
std::async 默认策略不等于 std::thread,延迟启动和共享状态需特别注意
用 std::async(std::launch::async, ...) 才真正启动新线程;默认策略 std::launch::async | std::launch::deferred 可能延迟执行(deferred),导致 future.get() 第一次调用才同步运行函数——这不是异步,是懒加载。
- 要严格异步,必须显式指定
std::launch::async,例如:auto f = std::async(std::launch::async, []{ return 42; }); - 多个
std::async共享同一std::promise不可行:每个std::promise只能对应一个std::future,且只能 set 一次 - 若需“多消费者”,得用
std::shared_future:调用future.share()后可复制,但所有副本共用同一就绪状态
手动实现 Promise 风格链式调用需封装状态机,别直接裸用 std::promise
原生 std::promise 不支持 .then()、.catch() 这类链式回调。强行模拟容易陷入资源管理混乱:比如在 then 回调里 new 一个新 std::promise,却忘了在线程退出时释放。
立即学习“C++免费学习笔记(深入)”;
- 最小可用链式结构至少需封装三部分:存储上一级
std::shared_future、用户回调函数、下一级std::promise(用于衔接) - 回调执行时机必须在上一级 future ready 后,且应在同一线程(如用
std::thread启动)或明确投递到某 executor,避免竞态 - 实际项目中建议用
std::jthread(C++20)替代std::thread,自动 join;并用std::stop_token支持取消,否则promise无法被“取消”
std::promise/std::future 的核心约束很硬:单次设置、不可复制、移动后失效。所有高级封装(Promise/A+、thenable)都得在这些边界内做状态调度——漏掉任何一条,都会在并发场景下突然卡死或崩溃。


















