下面是在 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
时,表明捕获机制中存在错误。
根据优化级别,捕获的范围是 this
的F
或 F()
的构造函数的堆栈帧.
关于C++20 协程捕获与引用奇怪的崩溃与 `unique_ptr`,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68072958/