结论:C++标准库无std::future::then(),手写链式异步需谨慎处理资源释放、异常传播与线程调度;强行模拟易因move语义、生命周期和异常导致崩溃,推荐用packaged_task+队列实现可控调度。

直接说结论:用 std::promise 和 std::future 手写链式异步任务,核心不是“怎么绑”,而是“谁来释放资源、谁来捕获异常、谁来决定线程调度”。标准库不提供 .then(),强行模拟容易在 move 语义、异常传播、生命周期上翻车。
为什么不能直接用 std::future::then()
因为 C++11/14/17 标准里压根没有 std::future::then() —— 这是常见误解。MSVC 曾私下实现过(已弃用),GCC/Clang 官方从未支持。你看到的所谓“链式调用”,基本都是自己封装的包装类或依赖第三方(如 boost::future 或 libunifex)。
- 标准
std::future是一次性消费的:调用get()后再访问会抛std::future_error(错误码std::future_errc::no_state) -
std::promise不能被拷贝,只能移动;一旦set_value()或set_exception()调用过,再次调用会 terminate - 没有内置机制把前一个
future的完成事件自动触发下一个promise,必须手动在线程中等待+转发
最简可行链式结构:两层 promise/future 手动桥接
想让 f1 完成后自动执行 f2,本质是监听 f1 状态并触发 f2 的 promise。不能靠轮询,得用线程+wait(),但要注意 detach 风险。
template<typename T, typename U>
auto then(std::future<T> f, std::function<U(T)> func) -> std::future<U> {
std::promise<U> p;
auto ret = p.get_future();
std::thread([f = std::move(f), func = std::move(func), p = std::move(p)]() mutable {
try {
U result = func(f.get()); // 阻塞等待 f 完成
p.set_value(std::move(result));
} catch(...) {
p.set_exception(std::current_exception());
}
}).detach(); // ⚠️ 这里极易泄漏或 crash,见下一条
return ret;
}
-
std::thread(...).detach()是危险操作:若主线程提前退出,子线程可能访问已销毁的栈对象(比如func捕获了局部变量) - 更稳妥做法是用
std::async(std::launch::async, ...),但要注意它返回的future默认延迟析构,可能隐式阻塞 - 所有 lambda 中的捕获必须显式用
=或&,且对std::promise和std::future必须用 move(它们不可拷贝)
异常传播和类型擦除的坑:别让 std::future::get() 成为单点故障
链式调用中任意一环抛异常,若没在对应 promise::set_exception() 中转发,就会在线程中 terminate。而且 std::future 不保存异常类型信息,下游只能统一用 std::exception_ptr 处理。
立即学习“C++免费学习笔记(深入)”;
- 不要在 lambda 里裸写
func(f.get())—— 若f.get()抛异常(比如前序 promise 被丢弃),会跳过set_exception直接终止线程 - 正确写法是先
f.wait(),再用f.valid() && f.wait_for(0s) == std::future_status::ready判断是否就绪,最后才get() - 如果链路超过 2 层,建议用
std::shared_ptr<std::promise>管理生命周期,避免 promise 在 future 就绪前被析构
真正实用的替代方案:用 std::packaged_task + 队列解耦调度
硬刚 promise/future 链式容易陷入线程管理泥潭。更可控的做法是把任务抽象为可执行单元,由统一调度器按依赖顺序提交到线程池:
using task_t = std::packaged_task<void()>;
std::queue<task_t> task_queue;
std::mutex q_mtx;
void submit(task_t t) {
std::lock_guard lk(q_mtx);
task_queue.push(std::move(t));
}
// worker 线程循环 pop 并执行
while (running) {
task_t t;
{
std::lock_guard lk(q_mtx);
if (!task_queue.empty()) {
t = std::move(task_queue.front());
task_queue.pop();
}
}
if (t) t();
}
- 每个
packaged_task自带get_future(),天然支持结果获取 - 链式逻辑变成“前序 task 执行完后,往队列 push 下一个 task”,彻底规避跨线程 move
promise的生命周期问题 - 异常可统一在 task 内部 try/catch 后调用
promise::set_exception,不会泄露到调度器
复杂点在于依赖关系建模——你得自己维护 DAG 或用引用计数标记前置任务完成状态。但这比修补 future 链的竞态要清晰得多。


















