本地 lambda 作为 co_spawn 的完成处理程序的生命周期,即具有 functor&& 的函数就足够了

问题描述 投票:0回答:1

问题

考虑到以下模式,我有点困惑或偏执:

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。


完成 MRE

#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();
}

c++ boost language-lawyer c++20 boost-asio
1个回答
0
投票

这是一个有趣的问题,对于无捕获关闭来说,是否没有与寿命终止相关的 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
实施的,那么我们必须依靠实施的质量。

© www.soinside.com 2019 - 2024. All rights reserved.