std::packaged_task不启动线程,仅是带返回值的任务容器,必须手动调用或交由std::thread/std::async执行;构造需严格匹配函数签名,get_future()须在执行前且仅调用一次,传入线程需std::move,异常由future.get()重抛。

直接说结论:std::packaged_task 本身不启动线程,它只是个“可异步执行的带返回值任务容器”,必须手动调用(operator())或交给 std::thread/std::async 才会真正执行;漏掉这一步,future.get() 就永远卡住。
构造时必须严格匹配函数签名
模板参数 Ret(Args...) 不是示意,是硬性约束:返回类型和每个参数类型都得一一对应,连 const/volatile/引用符都不能错。
-
int f(double, const std::string&)只能用std::packaged_task<int const std::string></int>,写成std::packaged_task<int std::string></int>编译失败 - 返回
void的任务,签名必须是void()或void(int)等,不能省略括号 - lambda 捕获列表不影响签名,但若按引用捕获局部变量,传给线程后极易悬垂——改用值捕获或确保生命周期覆盖整个执行期
get_future() 必须在调用前获取,且只能调用一次
这是最常踩的坑:get_future() 返回的是对内部共享状态的“只读视图”,一旦任务执行完毕,再调用就抛 std::future_error(错误码 std::future_errc::no_state)。
- 正确顺序:构造 →
task.get_future()→ 调用task(...)或移交线程 →fut.get() - 错误写法:
auto fut = task.get_future(); task(...); auto fut2 = task.get_future();—— 第二行直接崩溃 - 如果不确定是否已调用过,先用
task.valid()检查,避免空任务误操作
传给 std::thread 时必须显式 std::move
std::packaged_task 不可复制,只支持移动。直接传给 std::thread 构造函数会编译失败,不是警告,是硬性拒绝。
立即学习“C++免费学习笔记(深入)”;
- 正确写法:
std::thread t(std::move(task), arg1, arg2); - 错误写法:
std::thread t(task, arg1, arg2);—— 报错 “use of deleted function” - 移动后原变量失效:
task.valid()返回 false,不能再调用get_future()或operator() - 若用
t.detach(),必须确保任务所依赖的所有资源(如 lambda 捕获的对象)在线程结束前不会析构,否则未定义行为
异常不会丢失,但必须由 future.get() 主动触发重抛
任务里抛的异常不会被吞掉,而是被捕获并存进共享状态,等 fut.get() 调用时原样重抛——这点和裸线程完全不同,是它关键优势之一。
- 即使任务中
throw std::runtime_error("oops"),fut.get()也会抛出完全相同的异常 - 如果不调用
get(),异常就一直“沉睡”,不会终止程序,但资源可能泄漏 - 在多任务并发场景下,别只 catch 一个 future 的异常,要为每个
fut.get()单独包裹 try/catch
最难绷的是:std::packaged_task 看似封装了“异步”,实则把调度权全交给你——它不管理线程、不决定何时执行、不保证资源存活。你得自己盯住移动语义、生命周期、异常出口这三根弦,少一根都可能静默崩溃或死锁。


















