【问题标题】:Default reference parameters and lifetimes in coroutines协程中的默认引用参数和生命周期
【发布时间】:2021-12-24 04:36:57
【问题描述】:

我对传递给 C++ 协程的参数的生命周期感到困惑。 回复a previous question,聪明人表示

参数的生命周期是 [...] 调用者范围的一部分

现在,跟进,当传递默认参数时会发生什么

generator my_coroutine(string&& s = string()) {...}

因此,如果my_coroutine 是一个普通函数,那么s 将在其整个范围内有效。但是,如果 my_coroutine 是协程,这似乎不再成立。

特别是以下协程测试的结果让我感到惊讶:

#include <iostream>
#include <coroutine>

struct Test {
    int i = 3;
    Test() { std::cout << "test constructed\n";}
    Test(const Test&) = delete;
    Test(Test&&) = delete;
    ~Test() { std::cout << "test destructed\n"; }
    friend std::ostream& operator<<(std::ostream& os, const Test& t) { return os << t.i; }
};

template<class T>
generator<int> coro_test(T&& t = T()) {
  int i = 0;
  while(i++ < 3) co_yield i;
  if(i == t.i) co_yield 100;
}

int main () {
  auto gen = coro_test<Test>();
  while(gen.is_valid()) {
    std::cout << *gen << "\n";
    ++gen;
  }
  return 0;
}

结果:

test constructed
test destructed
1
2
3

PS:为了完整起见,这是我的generator

template<class T>
struct generator {
  struct promise_type;
  using coro_handle = std::coroutine_handle<promise_type>;

  struct promise_type {
    T current_value;
    auto get_return_object() { return generator{coro_handle::from_promise(*this)}; }
    auto initial_suspend() const noexcept { return std::suspend_never{}; }
    auto final_suspend() const noexcept { return std::suspend_always{}; }
    void unhandled_exception() const { std::terminate(); }

    template<class Q>
    auto yield_value(Q&& value) {
      current_value = std::forward<Q>(value);
      return std::suspend_always{};
    }
  };
private:
  coro_handle coro;
  generator(coro_handle h): coro(h) {}
public:
  bool is_valid() const { return !coro.done(); }
  generator& operator++() { if(is_valid()) coro.resume(); return *this; }

  T& operator*() { return coro.promise().current_value; }
  const T& operator*() const { return coro.promise().current_value; }

  generator(const generator&) = delete;
  generator& operator=(const generator&) = delete;
  ~generator() { if(coro) coro.destroy(); }
};

【问题讨论】:

  • IIRC 默认参数是在调用站点实现的,所以它不会改变任何东西。从生命周期的角度来看,它们仍然存在于调用者范围内的表达式中。一旦该表达式不再有效(例如,您暂停),那么参数也不再有效。
  • "在回答上一个问题时,smart people 表示" 'Smart people' 在这些词之后还有其他词。 “聪明人”也在一个特定问题的上下文中写了这些词,而你忽略了这个问题。

标签: c++ c++20 c++-coroutine


【解决方案1】:

正如“上一个问题”中所指出的,协程中发生的第一件事是将参数“复制”到协程拥有的存储中。但是,“副本”最终会根据签名中声明的类型进行初始化。也就是说,如果一个参数是一个引用,那么该参数的“副本”也是一个引用。

因此,接受引用参数的协程函数与任何接受引用参数的异步函数很相似:调用者必须确保被引用的对象在对象将要存在的整个时间内继续存在用过的。初始化引用的默认参数是调用者无法控制其生命周期(除了提供显式参数)的情况。

您创建的 API 本身就被破坏了。不要那样做。实际上,最好避免传递对任何类型的异步函数的引用,但如果这样做,永远不要给它们默认参数。

【讨论】:

    猜你喜欢
    • 2023-02-24
    • 1970-01-01
    • 2021-06-28
    • 2012-09-15
    • 1970-01-01
    • 2016-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多