#include <iostream>
#include <coroutine>
class eager {
public:
struct promise_type {
promise_type() { std::cout << "promise_type ctor" << std::endl; }
~promise_type() { std::cout << "~promise_type dtor" << std::endl; }
struct return_object {
return_object() { std::cout << "return_object ctor" << std::endl; }
~return_object() { std::cout << "~return_object dtor" << std::endl; }
operator eager() { return {}; }
};
auto get_return_object() noexcept { return return_object{}; }
constexpr auto initial_suspend() const noexcept { return std::suspend_never{}; }
constexpr auto final_suspend() const noexcept { return std::suspend_never{}; }
constexpr auto return_void() const noexcept {}
auto unhandled_exception() -> void { throw; }
};
};
auto coroutine() -> eager {
co_return;
}
auto main() -> int
{
coroutine();
return 0;
}
您可以在这里看到MSVC、clang和GCC的结果:https://godbolt.org/z/Yan9s9TPE
根据大量关于协程的文章,将coroutine()
转换为...
auto coroutine() -> eager {
eager::promise_type promise;
auto res = promise.get_return_object();
// initial suspend
promise.return_void();
// final suspend
return res;
}
乍一看,由于promise对象是先构造的,我以为它会是最后一个被析构的对象。
但是,MSVC和GCC显示相反的顺序:
// from MSVC/GCC
promise_type ctor
return_object ctor
~promise_type dtor
~return_object dtor
另一方面,clang显示了我所期望的:
// from clang
promise_type ctor
return_object ctor
~return_object dtor
~promise_type dtor
哪一个是对的?或者,只是标准没有规定承诺对象和返回对象的销毁顺序?
1条答案
按热度按时间pkln4tw61#
根据大量关于协同程序的文章
那么“很多关于协程的文章”是不正确的。
结果对象不在协程栈上。它 * 不能 * 在协程栈上,因为它是初始调用协程的 * 结果对象 *。
C标准对
get_result_object
的描述如下:表达式
promise.get_return_object()
用于初始化一个协程调用的glvalue结果或纯右值结果对象。get_return_对象的调用顺序在initial_suspend
调用之前,并且最多被调用一次。它发生在
initial_suspend
之前,并且只被调用一次,这就是它要说的,因此,函数的结果对象的其他一切都正常工作;在这种情况下,它只是在函数正确启动之前初始化,而不是在函数即将返回时初始化。根据C的一般规则,函数的result对象位于调用者的堆栈上,而不是被调用函数的堆栈上,因此当
promise.get_result_object()
被求值时,它正在初始化调用者提供的存储空间。main
丢弃表达式coroutine()
的结果。这意味着它将从纯右值结果对象显示一个临时变量,该临时变量的类型将是eager
。该临时变量将被销毁,但只有 after 控制返回到main
。这是棘手的部分:从
get_result_object()
返回的纯右值不是eager
,而是eager::promise::result_object
。初始化返回值需要执行从result_object
到eager
的隐式转换。这需要显示类型为result_object
的临时值来执行该转换。销毁临时文件的标准规则是:
临时对象在计算完整表达式([intro.execution])的最后一步被销毁,完整表达式([intro.execution])(词法上)包含临时对象的创建点。
但是......这里的“充分表达”是什么?
人们可能会假设它是
promise.get_result_object()
表达式。但根据C++的规则,它是一个“完整表达式”吗?这些规则是相当深奥和技术性的。人们可能会争辩说,promise.get_result_object()
被用来初始化一个对象,因此它实际上是一个“init声明符”。但“init声明符”是一段语法,并且X1 M17 N1 X没有被文本声明为“初始化声明符”。可以做出这样的论证:唯一一个“肯定”是完整表达式的表达式是
coroutine()
,因此,可以做出这样的论证:任何用于初始化返回值对象的临时变量都应该持续存在,直到控制返回给调用者。我认为标准的措辞是不够具体的,因此在澄清之前,两个版本都是合法的。Clang的版本更有意义(但不是你所说的原因),但其他版本至少是有争议的。