ant*_*ric 6 c++ boost boost-asio move-semantics
我对经常看到的 Boost.Asio 习惯用法感到困惑 - 像这样调用处理程序(函数对象):
std::move(handler)(param1, param2);
Run Code Online (Sandbox Code Playgroud)
这样写的原因是什么?我的理解是,这与
handler(param1, param2);
Run Code Online (Sandbox Code Playgroud)
除非处理程序的operator()方法是 ref 限定的&&(有关 ref 限定的信息,请参阅此处的“具有 ref 限定符的成员函数” )。这是值得期待的吗?我从未真正见过这个习语与 ref-qualified 配对operator(),所以这似乎是一个不太可能的解释。
例子:
std::move(op)-在函数中查找test_deferred()一般来说,对仅移动处理程序的显式支持与分配顺序相关,保证了Asio 能够:
如前所述,在调用完成处理程序之前必须释放所有资源。
这使得内存能够被回收用于代理内的后续异步操作。这允许具有长期异步代理的应用程序没有热路径内存分配,即使用户代码不知道关联的分配器也是如此。
具体来说,第二个例子
auto op = async_write_messages(socket, "Testing deferred\r\n", 5, asio::deferred);
Run Code Online (Sandbox Code Playgroud)
op定义一个带有类型的封装操作
asio::deferred_async_operation<
void(boost::system::error_code),
asio::detail::initiate_composed_op<void(boost::system::error_code),
void(asio::any_io_executor)>,
async_write_messages_implementation>
Run Code Online (Sandbox Code Playgroud)
看看其实现,我们确实看到了您的猜测:
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(Signature) CompletionToken>
auto operator()(
BOOST_ASIO_MOVE_ARG(CompletionToken) token) BOOST_ASIO_RVALUE_REF_QUAL;
template <BOOST_ASIO_COMPLETION_TOKEN_FOR(Signature) CompletionToken>
auto operator()(
BOOST_ASIO_MOVE_ARG(CompletionToken) token) const &;
Run Code Online (Sandbox Code Playgroud)
去除宏噪声后,在 c++20 中变为:
template <asio::completion_token_for<Signature> CompletionToken>
auto operator()(CompletionToken&& token) &&;
template <asio::completion_token_for<Signature> CompletionToken>
auto operator()(CompletionToken&& token) const&;
Run Code Online (Sandbox Code Playgroud)
-&&限定重载可优化执行。当您意识到延迟处理程序可能仅表示deferred_values要传递给用户处理程序时,这具有直观意义。这些(实际上是回调参数)复制或仅移动的成本也可能很高。
在本例中,deferred_async_operation实现一个延迟另一个异步操作启动的函数对象。启动函数采用的参数可能复制成本很高,或者只能移动。
事实上,rvalue-ref-qualified 版本支持那些 move-semantics,而 const-qualified 版本则不支持(再次为易读性而对 Asio 代码进行了大量编辑,并假设 C++14 或更高版本):
template <typename CompletionToken, std::size_t... I>
auto invoke_helper(CompletionToken&& token, std::index_sequence<I...>)
{
return asio::async_initiate<CompletionToken, Signature>(
std::move(initiation_), token, std::get<I>(std::move(init_args_))...);
}
template <typename CompletionToken, std::size_t... I>
auto const_invoke_helper(CompletionToken&& token, std::index_sequence<I...>) const&
{
return asio::async_initiate<CompletionToken, Signature>( //
initiation_t(initiation_), token, std::get<I>(init_args_)...);
}
Run Code Online (Sandbox Code Playgroud)
可以说,在不支持引用限定的代码中, 的非限定版本operator()将成功地将左值引用传递给启动函数。这甚至适用于仅移动类型,前提是参数由左值引用获取。如果可变,甚至可以将其移走。
更精确的转发允许启动,其中仅移动(“接收器”)参数也按值获取。
在避免复制的情况下,这对于优化应用程序的(取消)分配模式具有重要的好处。考虑一下如果所涉及的参数之一包含引用计数资源(例如shared_ptr)会发生什么。即使包装类型(例如deferred_async_operation)在调用后“立即”消失,当引用计数资源暂时具有非唯一引用计数时,分配/取消分配的顺序也可能存在差异。
我想将其归结为富有表现力的代码:仅调用一次的可调用对象应该表达“唯一调用”,就像任何其他仅移动类型信号“唯一所有权”一样std::move()。
有些地方很重要,像 Asio 这样的通用库不应该施加不必要的开销。
在示例中,async_write_messages_implementation是仅移动的,因为它包含unique_ptr成员。因此,通过const_invoke_helper将无法编译:https://godbolt.org/z/KTc5ooPhT
您可以通过更改unique_ptrto来修复它shared_ptr,但现在您遇到了所描述的问题,即在调用 to 期间资源的所有权不是唯一的,async_write_messages_implementation::operator()这导致reset()s 直到稍后才释放其资源:https ://godbolt.org/ z/n9fbE3zxh
| 归档时间: |
|
| 查看次数: |
179 次 |
| 最近记录: |