std::packaged_task是可移动不可复制的可调用对象包装器,需在定义时指定完整函数签名(如int()),调用get_future()获取一次性std::future,执行operator()后状态变为已满足,异常由future::get()重抛。

std::packaged_task 的基本用法和类型绑定
std::packaged_task 本质是个可调用对象包装器,把任意可调用体(函数指针、lambda、成员函数等)转成能返回 std::future 的任务。它必须在定义时就确定签名,比如 std::packaged_task<int></int> 只能包装无参、返回 int 的东西。
常见错误是忽略模板参数里的括号:写成 std::packaged_task<int></int> 会编译失败,必须带参数列表,哪怕为空 —— 因为它是对「函数类型」的封装,不是对「返回值类型」的封装。
- 签名必须完整:含返回类型 + 括号包围的参数列表,如
std::packaged_task<void std::string></void> - 不能用 auto 推导(除非用
decltype显式捕获),因为构造后类型已固定 - 被包装的可调用体参数类型必须严格匹配,不支持隐式转换(例如传
int给期望long的 task 会失败)
如何获取和使用对应的 std::future
每个 std::packaged_task 对象内部持有一个共享状态,调用其 get_future() 就能拿到一个关联的 std::future;这个 future 是**一次性**的,只能调用一次 get_future(),重复调用会抛 std::future_error(错误码为 std::future_errc::future_already_retrieved)。
future 的生命周期独立于 task:task 被 move 走或析构后,只要 future 还活着,且任务已被执行,就能正常取结果。
立即学习“C++免费学习笔记(深入)”;
- 务必在 task 执行前调用
get_future(),否则 future 永远不会就绪 - future 不可拷贝,只能移动;task 本身也不可拷贝,只支持移动构造/赋值
- 如果 task 被销毁而从未执行,对应 future 在
get()时会抛std::future_error(broken_promise)
在多线程中安全传递和执行 task
std::packaged_task 是可移动、不可复制的,天然适合跨线程传递。典型模式是:主线程构造 task + 获取 future → 把 task move 进线程函数或队列 → 子线程调用 operator() 执行它。
注意:task 的 operator() 是非 const 的,执行后内部状态变为“已满足”,再次调用会抛 std::future_error(no_state)。所以别重复执行同一个 task 实例。
- 用
std::thread时直接传 task 的右值引用:std::thread{std::move(task)} - 放进
std::queue前确保 queue 元素类型是std::packaged_task<...>,且 push 时用std::move - 不要在线程外保留对已 move 出的 task 的引用,否则行为未定义
捕获异常时 future 如何反映错误
如果被包装的可调用体抛出异常,std::packaged_task 会捕获并存入共享状态;对应 std::future::get() 调用时会重新抛出该异常(不是原始异常类型,而是封装后的 std::exception_ptr)。
这点和直接 try/catch 不同:你无法在 task 执行现场处理异常,所有错误传播都经由 future 完成。
- 即使 task 返回 void,抛异常也会让 future 进入异常就绪态
- future 的
wait()不会暴露异常,只有get()会触发重抛 - 若 task 内部吞掉了异常(比如用 try/catch + return),future 就不会报错,但可能返回意外值
真正容易被忽略的是:task 对象一旦 move 出作用域,你就失去了对它的唯一控制权;如果忘记在目标线程里执行它,或者执行逻辑被跳过(比如条件判断绕过了 () 调用),future 就永远阻塞在 get() 上 —— 这类死锁没有编译提示,调试时得靠超时或日志定位。


















