协程参数的生存期是多少?

2022-05-17 00:00:00 coroutine language-lawyer c++ c++20
#include <iostream>
#include <experimental/coroutine>
#include <string>
#include <thread>
struct InitialSuspend{
    bool await_ready(){
        return false;
    }
    bool await_suspend(std::experimental::coroutine_handle<> h){
          return false;
    }
    void await_resume(){
        
    }
};
struct FinalSuspend{
    bool await_ready() noexcept{
        return false;
    }
    void await_suspend(std::experimental::coroutine_handle<> h) noexcept{
       std::cout<<"FinalSuspend await_suspend
";
    }
    std::string await_resume() noexcept{
        std::cout<< "await_resume for FinalSuspend
";
       return "await_resume for FinalSuspend
";
    }
};
struct Task{
    struct promise_type;
    using coroutine_type = std::experimental::coroutine_handle<promise_type>;
    struct promise_type{
        auto initial_suspend(){
           return InitialSuspend{};
        }
        void unhandled_exception(){
            std::cout<<"unhandled_exception
";
            std::terminate();
        }
        auto final_suspend() noexcept{
           return FinalSuspend{};
        }
        // void return_value(std::string const& v){
        //    value_ = v;
        // }
        void return_void(){

        }
        auto get_return_object(){
            return Task{coroutine_type::from_promise(*this)};
        }
        std::string value_;
    };
    coroutine_type handler_;
};
struct AwaitAble{
    bool await_ready(){
        return false;
    }
    void await_suspend(std::experimental::coroutine_handle<> h){
        std::cout<<"await_suspend
";
    }
    std::string await_resume(){
       std::cout<<"await_resume
";
       return "abc";
    }
};
struct Observe0{
    Observe0(int v):id_(v){
        std::cout<< id_ <<"  constructor0
";
    }
    ~Observe0(){
        std::cout<< id_ <<" destroy0
";
    }
    Observe0(Observe0 const& v):id_(v.id_+1){
        std::cout<< id_<<" copy constructor0
";
    }
    Observe0(Observe0&& v):id_(v.id_+1){
       std::cout<< id_<<" move constructor0
";
    }
    int id_;
};
Task MyCoroutine(Observe0 p){
    auto r1 = co_await AwaitAble{};
}
int main(){
    Observe0  aa{1};  //#1
    auto r = MyCoroutine(aa); //#2
    std::cout<<"caller
";
    r.handler_.resume();
    r.handler_.destroy();
    std::cin.get();
}

output为:

1  constructor0
2 copy constructor0
3 move constructor0
await_suspend
2 destroy0
caller
await_resume
FinalSuspend await_suspend
3 destroy0
1 destroy0

我们可以使用上面的代码观察对象的创建或销毁。第一次打印发生在#1,它构造对象a。第二次打印发生在#2处的协程参数初始化时。第三次打印发生在协程参数COPY的初始化,它遵循以下规则:
[dcl.fct.def.coroutine#13]

当调用协程时,在初始化其参数之后([expr.call]),将为每个协程参数创建一个副本。对于cv T类型的参数,副本是cv T类型的变量,具有自动存储持续时间,它是从引用该参数的类型T的x值直接初始化的。

这三个对象都有其唯一的编号,这便于观察关联对象的生命周期。根据第五个打印,为协程参数调用析构函数,其名称为p。然而,根据[expr.await#5.1]

否则,控制流会返回到当前的协程调用方或恢复方([dcl.fct.Def.coroutine]),而不退出任何作用域([stmt.jump])。

意味着挂起协程并将控制权移交给调用方,协程的参数范围不被视为退出。因此,参数的生存期不应结束。为什么在第一次传递给协程的调用方之后调用参数的析构函数?是否应将其视为编译器中的错误?


解决方案

参数的生存期不是函数作用域的一部分;它是caller's scope:

的一部分

每个参数的初始化和销毁都在调用函数的上下文中进行。

这就是[dcl.fct.de.coroutine#13]存在的全部原因。为了让协程程序保持其参数,它必须拥有这些参数。这意味着必须将它们从参数复制/移动到本地自动存储中。

相关文章