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
。
在最终挂起点不挂起主要是针对生成器之类的东西,在它们流出协程结束后可能不需要承诺。
我有以下代码,我想将 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
。
在最终挂起点不挂起主要是针对生成器之类的东西,在它们流出协程结束后可能不需要承诺。