考虑到以下模式,我有点困惑或偏执:
void setup(boost::asio::io_context &context) {
const auto completion_handler = [](std::exception_ptr ptr) {
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
} else {
std::cout << "Completed without error" << std::endl;
}
};
boost::asio::co_spawn(context, coroutine_with_rethrow_completion_handler(), completion_handler);
}
completion_handler
的生命周期,即本地化可以吗?
AFAIK 当然,任何本地捕获都会很糟糕,因为当最终
boost::asio::io_context
将运行该处理程序时,它们将超出范围。但是那个处理程序(即函子,即 lambda 本身)的生命周期又如何呢?
boost::asio::co_spawn
采用&&
,据我所知应该是一个转发参考(这个boost函数的模板列表中有很多宏东西),并且完美地将完成函数转发到boost::asio的内部,并且 co_spawn
的文档没有说明有关完成令牌的任何生命周期问题。
所以我担心的是,最终,只有对该 lambda 的引用存储在
boost::asio::io_context
中,即 context
中,当我们实际在 io_context::run
中执行 main
中的 lambda 时,lambda 已经超出了范围在setup
,我们有UB。
#include <iostream>
#include <boost/asio.hpp>
boost::asio::awaitable<void> coroutine_with_rethrow_completion_handler() {
std::cout << "Coroutine executes with rethrow completion handler\n";
throw std::runtime_error("Test throw from coroutine!");
co_return;
}
void setup(boost::asio::io_context &context) {
const auto completion_handler = [](std::exception_ptr ptr) {
if (ptr) {
std::cout << "Rethrowing in completion handler" << std::endl;
std::rethrow_exception(ptr);
} else {
std::cout << "Completed without error" << std::endl;
}
};
boost::asio::co_spawn(context, coroutine_with_rethrow_completion_handler(), completion_handler);
}
int main() {
boost::asio::io_context context;
setup(context);
std::thread t([&context]() {
try {
while (true) {
context.run();
return;
}
} catch (std::exception &e) {
std::cerr << "Exception in context::run(): " << e.what() << "\n";
}
});
t.join();
}
这是一个有趣的问题,对于无捕获关闭来说,是否没有与寿命终止相关的 UB。所有这些都是因为闭包的行为必须与标准文档提供的参考代码类似(第 8.4.5.1 闭包类型):
struct Closure {
template<class T> auto operator()(T t) const { ... }
template<class T> static auto lambda_call_operator_invoker(T a) {
// forwards execution to operator()(a) and therefore has
// the same return type deduced
...
}
template<class T> using fptr_t =
decltype(lambda_call_operator_invoker(declval<T>())) (*)(T);
template<class T> operator fptr_t<T>() const {
return &lambda_call_operator_invoker;
}
};
如果我们有一个实例可以转换为函数指针,之后就不再需要该对象本身了 - 转换返回静态成员函数的地址。 SFINAE 会将闭包检测为可转换为
co_spawn
中的函数指针,之后仅存储指针。 如果是如何co_spawn
实施的,那么我们必须依靠实施的质量。