PPL create_task要求拷贝构造函数,如何适配仅可移动对象?
问题:将仅可移动对象传入PPL create_task的解决办法
问题描述
以下代码尝试使用PPL的create_task创建任务,lambda捕获了仅可移动对象,但编译失败:
#include <ppltasks.h> #include <future> #include <iostream> #include <thread> void third_party_lib_call_that_blocks(std::string message) { std::cout << message << "\n"; std::this_thread::sleep_for(std::chrono::seconds(1)); } class nocopy { public: explicit nocopy() {} nocopy(const nocopy& _Other) = delete; nocopy(nocopy&& _Other) = default; nocopy& operator=(const nocopy&) = delete; nocopy& operator=(nocopy&&) = default; }; int main() { std::string message = "hello"; nocopy n; auto t = concurrency::create_task([message = std::move(message), n = std::move(n)]() { std::cout << "start\n"; third_party_lib_call_that_blocks(message); std::cout << "end\n"; }); t.wait(); return 0; }
编译错误
C:/data/msvc/14.34.31931-Pre/include\ppltasks.h(4544): error C2338: static_assert failed: 'incorrect argument for create_task; must be either a callable object or a task_completion_event' <source>(30): note: see reference to function template instantiation 'Concurrency::task<Concurrency::details::_BadArgType> Concurrency::create_task<main::<lambda_1>>(_Ty,Concurrency::task_options)' being compiled with [ _Ty=main::<lambda_1> ] Execution build compiler returned: 2
问题根源:PPL的create_task要求传入的可调用对象必须具备拷贝构造函数,但捕获仅可移动对象后的lambda本身会变成仅可移动类型,无法满足要求。
解决办法
方法1:用std::shared_ptr包装仅可移动对象
将仅可移动对象托管到std::shared_ptr中,lambda捕获可拷贝的shared_ptr,间接持有原对象:
int main() { std::string message = "hello"; auto n = std::make_shared<nocopy>(); auto t = concurrency::create_task([message = std::move(message), n]() { std::cout << "start\n"; third_party_lib_call_that_blocks(message); std::cout << "end\n"; }); t.wait(); return 0; }
说明:std::shared_ptr本身是可拷贝的,lambda捕获后可以被create_task正常拷贝,而实际的nocopy对象存储在堆内存中,通过shared_ptr访问即可。
方法2:通过std::packaged_task中转
利用std::packaged_task可以接收仅可移动可调用对象的特性,将lambda包装后,把对应的std::future传入create_task:
int main() { std::string message = "hello"; nocopy n; auto task_func = [message = std::move(message), n = std::move(n)]() { std::cout << "start\n"; third_party_lib_call_that_blocks(message); std::cout << "end\n"; }; std::packaged_task<void()> pt(std::move(task_func)); auto future = pt.get_future(); // 启动线程执行packaged_task std::thread([pt = std::move(pt)]() mutable { pt(); }).detach(); // 基于future创建PPL任务 auto t = concurrency::create_task(std::move(future)); t.wait(); return 0; }
说明:packaged_task可以接管仅可移动的lambda,create_task可以接受std::future作为参数。需要注意必须单独启动线程执行packaged_task,否则future会一直处于等待状态。
针对asio::async_compose中self参数的处理
对于asio的async_compose传递的auto&& self(仅可移动的completion handler),同样可以用std::shared_ptr包装:
asio::async_compose<Token, void(std::error_code)>( [message = std::move(message)](auto&& self) mutable { // 将self包装到shared_ptr中 auto self_ptr = std::make_shared<decltype(self)>(std::move(self)); concurrency::create_task( [message = std::move(message), self_ptr]() mutable { auto ec = third_party_lib_call_that_blocks(std::move(message)); (*self_ptr).complete(ec); }); }, token, executor_);
说明:decltype(self)推导的是仅可移动的handler类型,用shared_ptr持有后,lambda可以安全拷贝shared_ptr,在任务执行时解引用调用complete即可完成异步回调。
内容的提问来源于stack exchange,提问作者MHebes
相关产品推荐
相关产品推荐

