C++ 第二次调用急切评估的协程会覆盖第一个输出

C++ second invocation of eagerly evaluated coroutine ovewrites first output

我有以下代码,我想将 lambda 或任何其他可调用对象传递给等待程序以在 await_suspend() 调用它。在使用 void(int) 时使用 co_await 可以正常工作,但是,当我尝试通过急切求值使用 co_return 从 returning 协程中获取 return 值时,该程序没有按预期运行,但是在使用惰性评估时,一切正常。但是,两次尝试都会导致 Invalid read by size 4。 AFAIK 一切都由文档完成 (https://en.cppreference.com/w/cpp/language/coroutines)。

#include <iostream>
#include <coroutine>
#include <tuple>
#include <functional>

#define DEBUG(x) std::cout << __FILE__ << ": " << __FUNCTION__ << ": " << __LINE__ << ": " << x << std::endl

// set lazy evaluation by uncommentting this line
//#define LAZY

/* create nonvoid task that will return value from coroutine through the promise_type */
template <typename RET>
struct nonvoid_task {
  struct promise_type;
  using handle_type = std::coroutine_handle<promise_type>;

  handle_type coro;

  /* coroutine with handler */
  nonvoid_task(handle_type cr) : coro(cr) {
      DEBUG("Creating non void coroutine");
  }

  /* destructor, no destroyign of handler here */
  ~nonvoid_task() {
      DEBUG("Destroying coroutine");
  }

  /* get return value from the coroutine handler */
  RET get() {
      DEBUG("Return value");
      DEBUG(std::hex << &(coro.promise().result) << std::dec);
#ifdef LAZY
      coro.resume();
#endif
      return coro.promise().result;
  }

  /* promise type which is used to return the value using co_return */
  struct promise_type {
    RET result;

    /* get return object */
    auto get_return_object() {
        auto task = nonvoid_task{handle_type::from_promise(*this)};
        DEBUG("get_return_object(): " << std::hex << &task << std::dec);
        return task;
    }

    /* set initial suspend to either lazy or eager eval */
#ifdef LAZY
    std::suspend_always initial_suspend() {
#else
    std::suspend_never initial_suspend() {
#endif
        DEBUG("");
        return {};
    }

    std::suspend_never final_suspend() noexcept {
        DEBUG("");
        return {};
    }

    /* get return value to the member result */
    void return_value(RET v) {
        DEBUG("Setting return value " << std::hex << &result << std::dec << " to " << v);
        result = v;
    }

    void unhandled_exception() {}
  };
};

/* template returning nonvoid_task that takes separate arguments */
template <typename RET, typename F, typename ... ARGS>
nonvoid_task<RET> ret_test(F to_invoke, ARGS ... args) {
    DEBUG("Not tuple");
    co_return std::invoke(to_invoke, args...);
}

int main() {
    /* function returning int for nonvoid_task */
    auto ret_func = [] (int a, int b) { return a * b; };

    /* invoke coroutines through overloaded templates */
    auto A = ret_test<int>(ret_func, 42, 2);
    auto B = ret_test<int>(ret_func, 42, 3);

    /* get value from coroutine A and B and print it */
    std::cout << A.get() << std::endl;
    std::cout << B.get() << std::endl;

    return 0;
}

不仅协程 A 和 B 打印 126 和 126,而不是 84 和 126,它们甚至共享相同的地址 coro.promise().result,根据文档,这不应该发生。使用延迟评估时不会发生这种情况。

Valgrind 输出:

==36893== Memcheck, a memory error detector
==36893== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==36893== Using Valgrind-3.17.0 and LibVEX; rerun with -h for copyright info
==36893== Command: ./a.out
==36893==
test.cpp: nonvoid_task: 21: Creating non void coroutine
test.cpp: get_return_object: 46: get_return_object(): 0x1ffefff5b0
test.cpp: initial_suspend: 56:
test.cpp: ret_test: 78: Not tuple
test.cpp: return_value: 67: Setting return value 0x5b3ac90 to 84
test.cpp: final_suspend: 61:
test.cpp: nonvoid_task: 21: Creating non void coroutine
test.cpp: get_return_object: 46: get_return_object(): 0x1ffefff5a8
test.cpp: initial_suspend: 56:
test.cpp: ret_test: 78: Not tuple
test.cpp: return_value: 67: Setting return value 0x5b3ad00 to 126
test.cpp: final_suspend: 61:
test.cpp: get: 31: Return value
test.cpp: get: 32: 0x5b3ac90
==36893== Invalid read of size 4
==36893==    at 0x401576: nonvoid_task<int>::get() (test.cpp:36)
==36893==    by 0x400BC9: main (test.cpp:91)
==36893==  Address 0x5b3ac90 is 16 bytes inside a block of size 48 free'd
==36893==    at 0x4C2DB0C: operator delete(void*) (vg_replace_malloc.c:802)
==36893==    by 0x400F73: ret_test(nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int)::_Z8ret_testIiZ4mainEUliiE_JiiEE12nonvoid_taskIT_ET0_DpT1_.frame*) [clone .actor] (test.cpp:80)
==36893==    by 0x400CF1: nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int) (test.cpp:77)
==36893==    by 0x400BA7: main (test.cpp:87)
==36893==  Block was alloc'd at
==36893==    at 0x4C2B751: operator new(unsigned long) (vg_replace_malloc.c:417)
==36893==    by 0x400C89: nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int) (test.cpp:80)
==36893==    by 0x400BA7: main (test.cpp:87)
==36893==
84
test.cpp: get: 31: Return value
test.cpp: get: 32: 0x5b3ad00
==36893== Invalid read of size 4
==36893==    at 0x401576: nonvoid_task<int>::get() (test.cpp:36)
==36893==    by 0x400BEE: main (test.cpp:92)
==36893==  Address 0x5b3ad00 is 16 bytes inside a block of size 48 free'd
==36893==    at 0x4C2DB0C: operator delete(void*) (vg_replace_malloc.c:802)
==36893==    by 0x400F73: ret_test(nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int)::_Z8ret_testIiZ4mainEUliiE_JiiEE12nonvoid_taskIT_ET0_DpT1_.frame*) [clone .actor] (test.cpp:80)
==36893==    by 0x400CF1: nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int) (test.cpp:77)
==36893==    by 0x400BBD: main (test.cpp:88)
==36893==  Block was alloc'd at
==36893==    at 0x4C2B751: operator new(unsigned long) (vg_replace_malloc.c:417)
==36893==    by 0x400C89: nonvoid_task<int> ret_test<int, main::{lambda(int, int)#1}, int, int>(main::{lambda(int, int)#1}, int, int) (test.cpp:80)
==36893==    by 0x400BBD: main (test.cpp:88)
==36893==
126
test.cpp: ~nonvoid_task: 26: Destroying coroutine
test.cpp: ~nonvoid_task: 26: Destroying coroutine
==36893==
==36893== HEAP SUMMARY:
==36893==     in use at exit: 0 bytes in 0 blocks
==36893==   total heap usage: 3 allocs, 3 frees, 72,800 bytes allocated
==36893==
==36893== All heap blocks were freed -- no leaks are possible
==36893==
==36893== For lists of detected and suppressed errors, rerun with: -s
==36893== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

你能告诉我我做错了什么吗?

你的问题在这里:

std::suspend_never final_suspend() noexcept

协程基本上是这样的:

Type actual_coroutine(args)
{
  promise p;
  initial-suspend-point;
  { /*Body of coroutine*/ }
  final-suspend-point;
}

“协程主体”是您放入协程的代码主体,其中 p 是协程的承诺对象。

如您所见,最终挂起点发生在协程体之外。但它也发生在与 p.

相同的范围内

但请注意,在最后一个暂停点之后没有任何内容。那么,当一个函数用完了事情要做时会发生什么?

它结束,将控制权返回给调用者。而且,其范围 中的所有堆栈对象都被销毁

p这样的对象;你的承诺。所以当你调用 get 时,它试图与之交谈的承诺对象 已经被销毁 .

糟糕。

因此,如果您想在协程有效结束后访问您的 promise 对象(在您的情况下,您会这样做),您 不得使用 suspend_never .您应该改用 suspend_always

在最终挂起点不挂起主要是针对生成器之类的东西,在它们流出协程结束后可能不需要承诺。