Oli*_*liv 5 c++ coroutine language-lawyer c++20
根据标准,协程只有在挂起时才会被销毁[dcl.fct.def.coroutine]
如果为未挂起的协程调用 destroy,则程序具有未定义的行为。
在协程 A 内评估等待表达式期间,假设此事件序列对应于[expr.await]/5.1:
协程 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?
}
Run Code Online (Sandbox Code Playgroud)
从这里可以看出,代码编译并运行似乎没有任何问题。
另一方面,这个明显等效的版本在这里崩溃了。
destroy当调用引用该协程的协程句柄的成员函数时,协程状态将被破坏...如果destroy为未挂起的协程调用该成员函数,则程序具有未定义的行为。
在您的示例中,从的协程句柄destroy调用,该句柄因 而被挂起。因此,该成员函数是为挂起的协程调用的,因此其中不存在未定义的行为。coroutine_Bcoroutine_Aco_awaitdestroy
事实上,Clang、GCC、MSVC 都可以很好地编译和执行代码: https: //gcc.godbolt.org/z/zxePvsvx1
另一方面,这个明显等效的版本在这里崩溃了。
这里的问题是coroutine_B.resumer包含nullptrGCC 中的地址,因此coroutine_B.resumer.destroy()是未定义的行为。演示: https: //gcc.godbolt.org/z/T8aa45ez1