如何将需可拷贝回调的异步函数适配为Boost.Asio CompletionToken?
问题:将仅接受std::function回调的第三方异步函数适配为Boost.Asio CompletionToken
现有不可修改的第三方异步函数std_function_cb,其回调参数类型固定为std::function<void()>。需要将该函数适配为Boost.Asio的CompletionToken,使其同时支持普通回调、future以及协程调用。
自行编写的适配代码出现编译错误,根源在于async_compose生成的self对象仅支持移动操作,不可拷贝,但std::function要求其存储的可调用对象必须具备拷贝能力。已知move_only_function等替代方案,但无法修改std_function_cb的参数类型,求可行解决办法。
第三方不可修改代码
namespace as = boost::asio; // BEGIN 3rd party code (不可修改) inline void std_function_cb(as::io_context& ioc, std::function<void()> cb) { // 伪实现:将回调投递到io_context执行 as::post(ioc, std::move(cb)); } // END 3rd party code (不可修改)
原适配代码(编译报错)
struct async_impl { as::io_context& ioc_; enum { first, second } state_ = first; template <typename Self> void operator()(Self& self) { switch (state_) { case first: state_ = second; std_function_cb(ioc_, std::move(self)); break; case second: self.complete(); break; } } }; template <typename CompletionToken> auto async_call( as::io_context& ioc, CompletionToken&& token ) -> typename as::async_result< typename std::decay<CompletionToken>::type, void() >::return_type { return as::async_compose< CompletionToken, void() >(async_impl{ioc}, token); }
期望支持的用法
int main() { std::cout << BOOST_VERSION << std::endl; { std::cout << "使用普通回调作为token" << std::endl; as::io_context ioc; async_call( ioc, [] { std::cout << "回调被调用" << std::endl; } ); ioc.run(); } { std::cout << "使用future作为token" << std::endl; as::io_context ioc; std::future<void> f = async_call(ioc, as::use_future); std::thread th {[&] { ioc.run(); }}; f.get(); std::cout << "future已解除阻塞" << std::endl; th.join(); } }
解决方案
核心思路是借助std::shared_ptr的拷贝语义,包装async_compose生成的移动-only self对象,让包装后的对象满足std::function的拷贝要求。具体实现如下:
修改后的适配代码
#include <memory> namespace as = boost::asio; // 第三方不可修改代码(同上) inline void std_function_cb(as::io_context& ioc, std::function<void()> cb) { as::post(ioc, std::move(cb)); } struct async_impl { as::io_context& ioc_; enum { first, second } state_ = first; template <typename Self> void operator()(Self& self) { switch (state_) { case first: state_ = second; // 用shared_ptr持有self的所有权,将拷贝语义传递给std::function auto self_ptr = std::make_shared<Self>(std::move(self)); std_function_cb(ioc_, [self_ptr]() mutable { // 触发self的后续逻辑 (*self_ptr)(); }); break; case second: self.complete(); break; } } }; template <typename CompletionToken> auto async_call( as::io_context& ioc, CompletionToken&& token ) -> typename as::async_result< typename std::decay<CompletionToken>::type, void() >::return_type { return as::async_compose< CompletionToken, void() >(async_impl{ioc}, token); }
原理说明
std::shared_ptr本身是可拷贝的,将移动-only的self对象通过std::move存入shared_ptr后,shared_ptr可以安全地被拷贝到std::function中。- 当第三方函数触发回调时,通过
shared_ptr访问内部的self对象,调用其operator()进入second状态,最终完成CompletionToken的回调逻辑。 - 这种方式既满足了
std::function的拷贝要求,又没有违反self对象的移动-only特性,因为self仅被移动一次到shared_ptr中,后续操作都是通过智能指针访问。
内容的提问来源于stack exchange,提问作者Takatoshi Kondo
相关产品推荐
相关产品推荐

