背景:这个问题是在我阅读康科罗的来源,特别是这条线时提出的。
问题:考虑以下代码:
#include <coroutine>
#include <stdexcept>
#include <cassert>
#include <iostream>
struct result_type {
result_type() {
std::cout << "result_type()\n";
}
result_type(result_type&&) noexcept {
std::cout << "result_type(result_type&&)\n";
}
~result_type() {
std::cout << "~result_type\n";
}
};
struct task;
struct my_promise {
using reference = result_type&&;
result_type* result;
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept {
return {};
}
task get_return_object();
void return_value(reference result_ref) {
result = &result_ref;
}
auto yield_value(reference result_ref) {
result = &result_ref;
return final_suspend();
}
void unhandled_exception() {}
};
struct task {
std::coroutine_handle<my_promise> handle{};
~task() {
if (handle) {
handle.destroy();
}
}
void run() {
handle.resume();
}
my_promise::reference result() {
return std::move(*handle.promise().result);
}
};
task my_promise::get_return_object() {
return { std::coroutine_handle<my_promise>::from_promise(*this) };
}
namespace std {
template <>
struct coroutine_traits<task> {
using promise_type = my_promise;
};
}
task f1() {
co_return result_type{};
}
task f2() {
co_yield result_type{};
// silence "no return_void" warning. This should never hit.
assert(false);
co_return result_type{};
}
int main() {
{
std::cout << "with co_return:\n";
auto t1 = f1();
t1.run();
auto result = t1.result();
}
std::cout << "\n==================\n\n";
{
std::cout << "with co_yield:\n";
auto t2 = f2();
t2.run();
auto result = t2.result();
}
}在上述守则中:
f1()和f2()都会启动一个协同线。立即挂起协同线,并将包含协同线句柄的task对象返回给调用方。coroutine的承诺包含result --指向result_type的指针。其目的是使它指向协同线的结果,当它完成。task.run(),该任务将恢复存储的协同线句柄。f1和f2分歧的地方:- `f1` uses `co_return result_type{}` to invoke `return_value` on the promise. Note that `return_value` accepts an r-value reference of `result_type`, therefore bounding it to a temporary.
- `f2` uses `co_yield result_type{}` to invoke `yield_value` on the promise. Same as `return_value`, it accepts an r-value
- In addition, `yield_value` returns `final_suspend()`, which in turn returns `std::suspend_always`, instructing the coroutine to suspend after yielding the value.- In both `return_value` and `yield_value`, `result` is set to point to the argument they received.但是,由于final_suspend也在co_return之后被调用(以及它的结果等待着),所以我希望使用co_return和co_yield之间没有什么区别。然而,编译器证明我错了
with co_return:
result_type()
~result_type
result_type(result_type&&)
~result_type
==================
with co_yield:
result_type()
result_type(result_type&&)
~result_type
~result_type注意,在上面的输出中,co_return版本构建一个结果,销毁它,然后从它中移动构造,调用未定义的行为。然而,co_yield版本似乎工作得很好,在从中移除构造后,只破坏了结果。
为什么这里的行为不同?
发布于 2021-04-20 04:46:46
您正在打破C++的一个基本规则:您编写了一个函数,该函数接受一个潜在的prvalue,并存储一个指针,该指针比给它们提供prvalue的函数调用更有效。实际上,每当您看到一个函数接受rvalue-引用(或const-lvalue-引用)并存储指向该对象的指针/引用(这将超过该函数)时,您应该认为该代码充其量是非常可疑的。
如果一个函数接受一个参数作为rvalue引用,这意味着您需要在该函数调用中使用它或从它移动。通常不期望Prvalue超过传递给它们的函数调用,所以要么使用它们,要么丢失它们。
在任何情况下,你所看到的行为都是你应该看到的。当协同线发出co_return时,它.退货。这意味着协同线块的主体已经退出。在块仍然存在时调用return_value,但是一旦这样做了,coroutine块及其所有自动变量(包括参数)就会消失。
这就是为什么从正常函数返回对自动变量的引用是个坏主意。对于co_return来说,这也是个坏主意,即使您间接地将该引用传递给调用方。
co_yield版本可以工作(您仍然不应该这么做,因为您不应该这样对待prvalue,但它是必需的),因为co_yield语句本身被yield_value的返回值告知要挂起。这保留了coroutine的堆栈,包括co_yield语句本身中的所有prvalue,直到coroutine恢复为止。
但是,您应该在yield_value函数中进行移动,就像通常对rvalue引用参数一样。
https://stackoverflow.com/questions/67172316
复制相似问题