std::packaged_task 是 C++11 引入的可调用对象包装器,用于异步执行并返回 std::future;适用于线程池提交带返回值任务、解耦构造与执行、callback 异步触发等场景。

packaged_task 是什么,适合在什么场景用
std::packaged_task 是 C++11 引入的包装器,把可调用对象(函数、lambda、绑定表达式等)封装成一个能异步执行、并带 std::future 返回值的任务。它不是线程本身,但天然配合 std::thread、std::async 或线程池使用——尤其当你需要「延迟执行 + 获取返回值」时,比裸写 std::promise 更简洁。
典型场景包括:向线程池提交带返回值的任务、解耦任务构造与执行时机、实现 callback 注册后异步触发。
怎么构造和调用 packaged_task
构造时必须指定完整签名(返回类型 + 参数类型),比如 std::packaged_task<int int></int>;传入的可调用对象必须匹配该签名。构造后它是个可移动、不可复制的对象。
- 不能直接用
operator()调用两次——调用后内部状态变为“已执行”,再次调用会抛出std::future_error(错误码为std::future_errc::no_state) - 调用前可通过
valid()检查是否持有有效任务,避免空任务误调 - 调用后立刻通过
get_future()获取的std::future才能取到结果;注意:get_future()只能调用一次,重复调用也抛std::future_error
示例:
立即学习“C++免费学习笔记(深入)”;
std::packaged_task<int(int, int)> task([](int a, int b) { return a + b; });
auto fut = task.get_future(); // 必须在调用前获取
task(3, 4); // 执行任务
std::cout << fut.get(); // 输出 7
如何在线程中安全执行 packaged_task
常见错误是把 std::packaged_task 对象直接传给 std::thread 构造函数,导致移动语义失效或生命周期问题。正确做法是显式移动(用 std::move),且确保原变量不再访问。
- 传给线程时必须用
std::move(task),否则编译失败(因为packaged_task不可复制) - 如果任务捕获了局部变量(如 lambda 中按引用捕获),要确保这些变量在线程执行期间仍有效,否则引发未定义行为
- 若在线程内不调用任务,
std::future会永远阻塞;务必保证任务被且仅被调用一次
安全写法:
std::packaged_task<int()> task([]{ return 42; });
auto fut = task.get_future();
std::thread t(std::move(task));
t.detach(); // 或 t.join()
std::cout << fut.get(); // 可能阻塞,直到线程执行完 task()
和 std::function、std::promise 的关键区别在哪
std::packaged_task 和 std::function 都能包装可调用对象,但前者自带 std::future 接口,后者没有;和 std::promise 比,它省去了手动 set_value 的步骤——执行即自动 fulfill future。
- 用
std::function+std::promise组合也能实现类似效果,但代码更啰嗦,且容易忘记调用set_value或重复调用 -
packaged_task的get_future()返回的是std::future,不是std::shared_future;如需多处等待,得自己转成shared_future - 移动后原对象变为无效状态(
valid() == false),这点和std::thread类似,容易忽略导致后续误用
真正容易被忽略的是:任务对象和 future 的生命周期必须协调好。future 不能早于任务执行完毕就被销毁(否则可能丢失异常信息),任务对象也不能在 future 还没 get 前就彻底离开作用域(虽然 move 后原对象无效,但 future 仍持有共享状态)。


















