boost::asio::awaitable<void>co例程是否结束而没有co_return未定义行为?

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

没有 co_return 未定义行为(UB)的 boost::asio::awaitable co 例程的结尾是否下降?我在 boost 文档中找不到任何相关内容。 Cppreference 说,当没有

Promise::return_void()
时它的 UB,与它相反的是
promise.return_void()
,它说对于所有情况,包括
co_return
和协同例程结束时的下降,也会被调用。

但是我发现了不同的行为,其中一个建议是 UB: 编译器资源管理器上的代码

#include <iostream>
#include <thread>

#include <boost/asio.hpp>

boost::asio::awaitable<void>  coroutineA() {
    std::cout << "coroutineA" << std::endl;
}

boost::asio::awaitable<void> coroutineB() {
    std::cout << "coroutineB" << std::endl;
    co_return;
}

int main() {
    boost::asio::io_context context;

    auto waitA= boost::asio::co_spawn(context, 
      coroutineA(), boost::asio::use_future); //Seems to cause UB
    auto waitB = boost::asio::co_spawn(context, 
      coroutineB(), boost::asio::use_future);

    std::thread t([&context] () {
        context.run();
    });
    waitA.get();
    waitB.get();
    t.join();
}

执行

coroutineA
会导致这个简单代码崩溃,在其他演示案例中,它会导致非0程序代码但非阻塞执行。

我能找到的唯一线索是在 boost 代码中:Boost 1.82

asio/impl/awaitable.hpp
void return_void()
template <typename Executor> class awaitable_frame<void, Executor>
定义了
template <typename Executor> class awaitable_frame<awaitable_thread_entry_point, Executor>
。根据我的理解,前一个应该是我们为您的两个协程提供的承诺类型。

Cppreference 指出,

co_return
和末端掉落都会调用
promise.return_void()
,即
awaitable_frame::return_void()
,我认为这是定义的。

那么,如果没有 co_return 未定义行为(UB),boost::asio::awaitable co 例程的结尾是否会下降?如果是,为什么?有人可以给我指出文档,或者更好地向我展示我的推理中的缺陷吗?

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

在阅读了 C++20 草案和进一步的实验之后,我有足够的信心说:

没有 co_return 未定义行为 (UB) 的 boost::asio::awaitable co 例程的结尾是否下降?

TL;博士

这要看情况。如果函数体中有任何其他协程关键字,例如

co_yield
co_return
(如 if 分支中)或
co_await
,那么它不是 UB。如果函数中没有任何co例程关键字,则为UB。对于所有 C++20 协程都是如此,其中使用的 Promise 定义了
return_void()

示例

#include <iostream>
#include <thread>

#include <boost/asio.hpp>

boost::asio::awaitable<void>  coroutineA(boost::asio::io_context& context) {
    boost::asio::steady_timer t(context);
    t.expires_after(std::chrono::seconds(1));
    co_await t.async_wait(boost::asio::use_awaitable);//now this is a coroutine, falling of ok
    std::cout << "coroutineA" << std::endl;
}//only ok thanks to 2 lines above

boost::asio::awaitable<void> coroutineB() {
    if (false) {
        co_return; //Not this is a coroutine, falling of ok
    }
    std::cout << "coroutineB" << std::endl;
}//only ok thanks to 3 lines above

int main() {
    boost::asio::io_context context;

    auto waitA= boost::asio::co_spawn(context, 
      coroutineA(context), boost::asio::use_future); 
    auto waitB = boost::asio::co_spawn(context, 
      coroutineB(), boost::asio::use_future);

    std::thread t([&context] () {
        context.run();
    });
    waitA.get();
    waitB.get();
    t.join();
}

推理

C++20 标准及更高版本(我使用草案 N4861 和最新版本)在 9.5.4 协程定义 [dcl.fct.def.coroutine] 中 1:

中进行了说明

如果一个函数的函数体包含一个协程返回语句(8.7.4)、一个等待表达式,则该函数是一个协程 (7.6.2.3),或yield表达式(7.6.17)。

和6

在 Promise 类型的范围内查找非限定 ID return_void 和 return_value。如果两者都 发现该程序格式错误。 [注意:如果发现unqualified-id return_void,则从末尾流出 协程的 相当于没有操作数的 co_return。否则,从协程的末尾流出 导致未定义的行为(8.7.4)。 — 尾注]

因此,假设所使用的 Promise 正确定义了

return_void()
,例如
boost::asio::awaitable<void>
确实如此,即在这种情况下,协程 的末尾掉落是可以的。

但是如果它的function主体包含即“包含”当前3个协程关键字之一:co_return

co_yield
co_await
,那么它只是一个
协程
。他们是否真的被处决并不重要。 否则我们只有一个函数,而不是协程,并且非空函数末尾的下降是未定义的行为(UB),即真的非常糟糕(崩溃,有时工作等等)。

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