std::packaged_task构造需显式指定函数签名模板参数,不可类型推导;是一次性对象,调用operator()或get_future()后状态改变,必须按构造→get_future()→移动调用顺序使用。

std::packaged_task 构造时必须显式指定函数签名
模板参数不是类型推导出来的,而是你必须写死的调用签名,比如 std::packaged_task。哪怕传入的是 lambda 或成员函数,也得按它实际可被调用的形式来声明——少一个 const、多一个 &,编译就报错。
常见错误现象:error: no matching constructor,往往是因为签名和可调用对象不匹配。例如用 std::packaged_task 去包一个带参数的 lambda;或用 int() 去包返回 void 的函数。
- 普通函数:直接传函数名,如
compute_sum(自动退化为函数指针) - lambda:必须可移动,禁止捕获局部变量的引用(
[&x]{...}会出问题),推荐用值捕获[=]或显式列出[x, y] - 成员函数:不能直接传
&MyClass::method,得用std::bind(&MyClass::method, &obj, std::placeholders::_1)或 lambda[&obj](int x){ return obj.method(x); }
封装后不能直接调用两次,valid() 和 get_future() 都有严格时序要求
std::packaged_task 是一次性对象:调用 operator() 后内部状态置为“已执行”,再调就会抛 std::future_error(错误码 std::future_errc::no_state)。而 get_future() 只能调一次,且必须在 operator() 调用前——否则也抛同个错误。
所以正确顺序只能是:构造 → get_future() → 移动到线程/async/手动调用 operator()。
- 别在构造后忘了调
get_future(),否则 future 为空,result.get()会卡死或未定义行为 - 别把 task 对象复制给多个线程——它不可复制,只能 move;重复 move 会导致空任务,
valid()返回 false - 调试时可用
if (task.valid()) task(42);避免空任务误触发
异步执行要靠 std::thread 或 std::async,不是封装完就自动跑
封装只是准备好了可调用体,不调度就不会执行。常见做法是把它 move 进 std::thread 或交给 std::async,但二者语义不同:
-
std::thread t(std::move(task), arg1, arg2);:任务立刻在新线程启动,主线程需自行管理生命周期(t.join()或t.detach()) -
auto fut = std::async(std::launch::async, std::move(task), arg1);:更轻量,future 自带线程管理和异常传播,但注意默认策略可能是延迟启动(std::launch::deferred),务必显式写std::launch::async - 也可以同步调用:
task(123);,此时 future 仍有效,result.get()立刻返回,适合测试或条件分支中降级路径
结果获取依赖 future 的生命周期和调用时机
std::future 和 std::packaged_task 绑定的是同一块共享状态,但 future 可以脱离 task 独立存在。只要 task 执行过(无论同步异步),future 就能取结果;但 get() 只能调一次,第二次会抛 std::future_error(std::future_errc::no_state)。
容易被忽略的点:future 必须在 task 所在线程完成前保持存活。如果 task 在子线程执行,而主线程里 future 被析构了,那结果就丢了——不会 crash,但后续等待无响应。
- 用
result.wait_for(1s) != std::future_status::ready做超时判断,比无脑get()更健壮 - 若任务可能抛异常,
get()会重新抛出,建议用 try/catch 包裹 - 不要把 future 存在局部作用域里然后期望跨线程访问——它不线程安全,只保证单次
get()或wait()
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











