C++协同程序中承诺和返回对象的销毁顺序

myzjeezk  于 2023-01-18  发布在  其他
关注(0)|答案(1)|浏览(126)
#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

哪一个是对的?或者,只是标准没有规定承诺对象和返回对象的销毁顺序?

pkln4tw6

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_objecteager的隐式转换。这需要显示类型为result_object的临时值来执行该转换。
销毁临时文件的标准规则是:
临时对象在计算完整表达式([intro.execution])的最后一步被销毁,完整表达式([intro.execution])(词法上)包含临时对象的创建点。
但是......这里的“充分表达”是什么?
人们可能会假设它是promise.get_result_object()表达式。但根据C++的规则,它是一个“完整表达式”吗?这些规则是相当深奥和技术性的。人们可能会争辩说,promise.get_result_object()被用来初始化一个对象,因此它实际上是一个“init声明符”。但“init声明符”是一段语法,并且X1 M17 N1 X没有被文本声明为“初始化声明符”。
可以做出这样的论证:唯一一个“肯定”是完整表达式的表达式是coroutine(),因此,可以做出这样的论证:任何用于初始化返回值对象的临时变量都应该持续存在,直到控制返回给调用者。
我认为标准的措辞是不够具体的,因此在澄清之前,两个版本都是合法的。Clang的版本更有意义(但不是你所说的原因),但其他版本至少是有争议的。

相关问题