C++20 协程捕获与引用奇怪的崩溃与 `unique_ptr`

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

下面是在 main 中使用第 (2) 行版本(并且第 (1) 行被注释)时崩溃的代码。奇怪的是,这段代码使用模仿第 (2) 行行为的简单替换实现(第 (1) 行)来编译罚款。当然,如果这是一个未定义的行为,它就不能有一个很好的解释,但我不明白为什么它会崩溃。基本上,它是 C++ 中的生成器实现协程,通过引用捕获进行测试。它始终有效,除非与 unique_ptr 一起使用(原始指针有效)。只是,为什么?

#include <iostream>
#include <coroutine>
#include <cassert>
#include <optional>
#include <memory>

using std::cout;
using std::endl;

template<typename T>
class generator
{
public:
    struct promise_type
    {
        std::optional<T> t_;

        promise_type() = default;
        ~promise_type() = default;

        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
        generator get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }

        std::suspend_always yield_value(T t) { t_ = t; return {}; }
        void return_void() {}
    };
private:
    std::coroutine_handle<promise_type> h_;

    generator(std::coroutine_handle<promise_type> h) : h_(h) {}

public:
    generator() = default;

    // ------ Prevent copies
    generator(const generator&) = delete;
    generator& operator=(const generator&) = delete;

    // ------ Allow moves
    generator(generator&& other) noexcept
        : h_(move(other.h_)) // move may be unnecessary, coroutine_handle acts like a lightweight pointer
    {
        other.h_ = {}; // Unlink handle in moved generator
                       // move() does not guarantee to destroy original value
    }

    generator& operator=(generator&& other) noexcept
    {
        h_ = move(other.h_);
        other.h_ = {};
        return *this;
    }

    ~generator()
    {
        if(h_)
        {
            h_.destroy();
            h_ = {};
        }
    }

    bool is_resumable() const
    {
        return h_ && !h_.done();
    }

    bool operator()()
    {
        return resume();
    }

    bool resume()
    {
        assert(is_resumable());

        h_();

        return !h_.done();
    }

    [[nodiscard]] const T& get() const
    {
        return h_.promise().t_.value();
    }

    [[nodiscard]] T& get() // Allow movable
    {
        return h_.promise().t_.value();
    }
};

struct F
{
    /*F(const std::function<generator<int>()>& del)
    {
        handle = del();
    }*/

    template<typename T>
    F(T del)
    {
        handle = del();
    }

    ~F() { cout << "dtor" << endl; }

    generator<int> handle;
};

template<typename T>
struct UniquePtr
{
    UniquePtr(T* t) : t_(t) {}

    UniquePtr(UniquePtr&&) = delete;
    UniquePtr(const UniquePtr&) = delete;
    UniquePtr& operator=(UniquePtr&&) = delete;
    UniquePtr& operator=(const UniquePtr&) = delete;

    ~UniquePtr() { delete t_; }

    T* operator->() const { return t_;}

private:
    T* t_;
};

int main()
{
    int x = 10;
    auto a = [&]() -> generator<int> {
        x = 20;
        co_yield x;
    };

    //UniquePtr<F> ptr(new F(a)); // (1)
    std::unique_ptr<F> ptr(new F(a)); // (2)

    generator<int>& gen = ptr->handle;
    gen();
    cout << gen.get() << "/" << x << endl;

    return 0;
}

编辑。 :

Godbolt 也会崩溃(错误 139),链接如下: https://godbolt.org/z/cWYY8PKx4 。也许这是一个关于 std::unique_ptr 优化的 gcc 实现问题?我无法在 Godbolt 上的其他编译器上进行测试,clang 上不支持协程。

最佳答案

虽然仍不清楚为什么会发生这种情况,但似乎std::suspend_always initial_suspend() { return {}; }当与 lambda 结合使用并结合作用域 unique-ptrs 的优化时,也会在错误的作用域中错误地执行参数捕获。

稍微修改的变体:

struct F
{
    template<typename T>
    F(T del)
    {
        auto y = 0;
        cout << "&y   = " << &y << endl;
        handle = del();
    }

    ~F() { cout << "dtor" << endl; }

    generator<int> handle;
};

int main()
{
    int x = 10;
    cout << "&x   = " << &x << endl;
    auto a = [&]() -> generator<int> {
        cout << "[&x] = " << &x << endl;
        co_yield x;
    };

    auto ptr = std::make_unique<F>(a);

    generator<int>& gen = ptr->handle;
    gen();

    a()();

    return 0;
}

输出:

&x   = 0x7fff46a718cc
&y   = 0x7fff46a71834
[&x] = 0x7fff46a71840
[&x] = 0x7fff46a718cc
dtor

F() 构造函数的堆栈框架内的第二个和第三个输出点。这仅适用于 &y不过,最后一个执行正确。

当使用 std::suspend_never initial_suspend() { return {}; } 时,它的行为也正确。 ,或者转换为显式 std::function 时,表明捕获机制中存在错误。

根据优化级别,捕获的范围是 thisFF() 的构造函数的堆栈帧.

关于C++20 协程捕获与引用奇怪的崩溃与 `unique_ptr`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68072958/

相关文章:

c++ - const static auto lambda 与引用捕获一起使用

c++ - 如何在 emacs 中使用公司模式、irony 后端和 irony header

c++ - 将打包的半字节组合成打包的字节

c - 需要知道我错过了哪些 gcc 开关来编译 C 脚本

c++ - 如何将shared_ptr<void>转换为另一种类型?

c++ - 你如何从远程机器获取IP地址?

c++ - 为什么成员函数的符号很弱?

c - 在 C 中定义函数之前使用函数时的隐式声明,为什么编译器不能解决这个问题?

c++ - std::atomic 和 std::condition_variable 等待、notify_* 方法的区别

c++ - 在 C++ 中通过指向其字节表示的指针修改对象有什么限制?