c++ - 当右侧是临时的时,co_return 与 co_yield

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

背景:这个问题是在我阅读 cppcoro 的来源时出现的。 ,具体来说this line .

问题:考虑以下代码:

#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();
    }
}

在上面的代码中:

  1. 调用 f1()f2() 都会启动一个协程。协程立即挂起,并将包含协程句柄的任务对象返回给调用者。协程的 Promise 包含 result - 一个指向 result_type指针。目的是让它在协程完成时指向其结果。

  2. task.run() 在返回的任务上调用,这将恢复存储的协程句柄。

  3. 这里是 f1f2 的分歧点:

    • f1 使用 co_return result_type{} 调用 Promise 上的 return_value。请注意,return_value 接受 result_type 的右值引用,因此将其绑定(bind)到临时值。
    • f2 使用 co_yield result_type{} 调用 Promise 上的 yield_value。与return_value相同,它接受一个右值
      • 此外,yield_value 返回 final_suspend(),后者又返回 std::suspend_always,指示协程在生成后挂起值。
    • return_valueyield_value 中,result 均设置为指向它们收到的参数。

但是,由于在 co_return 之后也会调用 final_suspend(并等待其结果),因此我预计使用 co_return 之间没有区别和一个co_yield。然而,compiler proved me wrong :

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 版本似乎工作正常,仅移动构建之后才破坏结果。

为什么这里的行为不同?

最佳答案

您违反了 C++ 的基本规则:您编写了一个接受潜在纯右值的函数,并存储了一个指针,该指针比为它们提供纯右值的函数调用生命周期更长。事实上,每当您看到一个函数接受右值引用(或常量左值引用)并存储指向该对象的指针/引用(该对象的生命周期将比该函数长)时,您应该认为该代码高度 充其量是可疑的。

如果函数将参数作为右值引用,则意味着您需要在该函数调用中使用它或从它移出。通常预计纯右值不会比它们所传递到的函数调用的生命周期更长,因此您要么使用它们,要么丢失它们。

无论如何,您所看到的行为正是您应该看到的。当协程发出 co_return 时,它...返回。这意味着协程 block 的主体已经退出return_value 在 block 仍然存在时被调用,但是一旦完成,协程 block 及其所有自动变量(包括参数)就会消失。

这就是为什么从普通函数返回对自动变量的引用是一个坏主意。对于 co_return 来说,这同样是一个糟糕的主意,即使您间接将该引用引导到调用者。

co_yield 版本可以工作(你仍然不应该这样做,因为这不是你应该如何处理纯右值,但它需要工作),因为 co_yield 语句本身被 yield_value 的返回值告知暂停。这将保留协程的堆栈,包括 co_yield 语句本身内的任何纯右值,直到协程恢复为止。

但同样,您应该在 yield_value 函数中进行移动,就像通常对右值引用参数所做的那样。

关于c++ - 当右侧是临时的时,co_return 与 co_yield,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67172316/

相关文章:

c++ - 是否计划为 future 的 C++ 版本修订 std::allocator 接口(interface)?

C++20 协程 : implementing an awaitable future

c++ - 与 C++20 协程一起使用时,boost::asio::use_awaitable 和 boost::asio::deferred 之间有什么区别?

c++ - Qt 无法弄清楚如何在我的程序中线程化我的返回值

c++ - 在 C/C++ 中,如何确定库是否为静态链接

C++ 函数模板重载 : empty braces vs explicit int

c++ - C++20 中是否有assign_or_inserter/insert_or_assigner?

c++ - 如何在 Mac 上使用 c++2a 编译生成器和协程

c++ - 试图在数组中获取一个两倍于平均值的数字

c++ - 如何在 PhysX 中创建非碰撞刚体