首页 > 解决方案 > 在评估内部等待表达式期间可以销毁协程吗?

问题描述

根据标准,协程只有在挂起时才会被销毁[dcl.fct.def.coroutine]

如果为未挂起的协程调用destroy,则程序具有未定义的行为。

在协程 A 内的await 表达式求值期间,假设此事件序列对应于[expr.await]/5.1

  1. 协程 A 被挂起,因为await-ready为假;
  2. 在将控制权交给协程 A 调用者之前,协程 B 被恢复;
  3. 一旦 B 也被挂起,控制流将返回给协程 A 调用者。

协程 A 可以在协程 B 恢复后挂起之前销毁吗?

示例代码:

#include <coroutine>

using namespace std;

struct task
    {
    struct promise_type;

    using handle_type = coroutine_handle<promise_type>;

    struct promise_type
        {
        handle_type resumer = nullptr; 
        auto
        get_return_object(){
            return task{handle_type::from_promise(*this)};
            }

        auto 
        initial_suspend(){
            return suspend_always {};
            }

        auto 
        unhandled_exception(){}

        auto
        final_suspend(){
            return suspend_always{};
            }

        void 
        return_void() {}

        };


   handle_type handle;

   void await_resume(){
       handle.resume();
   }

   auto
   await_suspend(handle_type h){
       handle.promise().resumer = h;
       return handle;
   }
   auto
   await_ready(){
       return false;
   }

    };


int main(){

  task coroutine_B = [&coroutine_B]() ->task
            {
            coroutine_B.handle.promise().resumer.destroy();
            co_return;
            }();//coroutine B supended at initial suspend

  task coroutine_A = [&coroutine_B]() ->task 
            {
            co_await coroutine_B;//set coroutine_B resumer to coroutine_A handle
                                 //then resume coroutine_B.
            }();//coroutine A supended at initial suspend.

  coroutine_A.handle.resume();//execute co_await coroutine_B;
                              //is this UB?

}

从这里可以看出,代码可以编译并且运行起来似乎没有任何问题。

另一方面,这个显然等效的版本在这里崩溃。

标签: c++language-lawyercoroutinec++20

解决方案


dcl.fct.def.coroutine#11中的标准说

协程状态在以下情况下被破坏......destroy引用协程的协程句柄的成员函数被调用......如果destroy为未挂起的协程调用,则程序具有未定义的行为。

在您的示例中,是为 的协程句柄destroy调用的,该句柄由于. 所以成员函数是为一个挂起的协程调用的,所以其中没有未定义的行为。coroutine_Bcoroutine_Aco_awaitdestroy

事实上,Clang、GCC、MSVC 都可以很好地编译和执行代码:https ://gcc.godbolt.org/z/zxePvsvx1

另一方面,这个显然等效的版本在这里崩溃。

这里的问题是在 GCC中coroutine_B.resumer包含地址,所以是未定义的行为。演示:https ://gcc.godbolt.org/z/T8aa45ez1nullptrcoroutine_B.resumer.destroy()


推荐阅读