C++ 20 个带有 PyBind11 的协程
我正在尝试使用 PyBind11 获得一个简单的基于 C++ 20 的生成器模式。这是代码:
#include <pybind11/pybind11.h>
#include <coroutine>
#include <iostream>
struct Generator2 {
Generator2(){}
struct Promise;
using promise_type=Promise;
std::coroutine_handle<Promise> coro;
Generator2(std::coroutine_handle<Promise> h): coro(h) {}
~Generator2() {
if(coro)
coro.destroy();
}
int value() {
return coro.promise().val;
}
bool next() {
std::cout<<"calling coro.resume()";
coro.resume();
std::cout<<"coro.resume() called";
return !coro.done();
}
struct Promise {
void unhandled_exception() {std::rethrow_exception(std::move(std::current_exception()));}
int val;
Generator2 get_return_object() {
return Generator2{std::coroutine_handle<Promise>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always yield_value(int x) {
val=x;
return {};
}
std::suspend_never return_void() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
};
};
Generator2 myCoroutineFunction() {
for(int i = 0; i < 100; ++i) {
co_yield i;
}
}
class Gen{
private:
Generator2 myCoroutineResult;
public:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
int next(){
return (myCoroutineResult.next());
}
};
PYBIND11_MODULE(cmake_example, m) {
pybind11::class_<Gen>(m, "Gen")
.def(pybind11::init())
.def("next", &Gen::next);
}
但是我收到错误:
进程已完成,退出代码为 139(被信号 11:SIGSEGV 中断)
c++ 协程、coroutine_handles、co_yield 等是否是 PyBind11 尚不支持的低级事物?
I'm trying to get a simple C++ 20 based generator pattern work with PyBind11. This is the code:
#include <pybind11/pybind11.h>
#include <coroutine>
#include <iostream>
struct Generator2 {
Generator2(){}
struct Promise;
using promise_type=Promise;
std::coroutine_handle<Promise> coro;
Generator2(std::coroutine_handle<Promise> h): coro(h) {}
~Generator2() {
if(coro)
coro.destroy();
}
int value() {
return coro.promise().val;
}
bool next() {
std::cout<<"calling coro.resume()";
coro.resume();
std::cout<<"coro.resume() called";
return !coro.done();
}
struct Promise {
void unhandled_exception() {std::rethrow_exception(std::move(std::current_exception()));}
int val;
Generator2 get_return_object() {
return Generator2{std::coroutine_handle<Promise>::from_promise(*this)};
}
std::suspend_always initial_suspend() {
return {};
}
std::suspend_always yield_value(int x) {
val=x;
return {};
}
std::suspend_never return_void() {
return {};
}
std::suspend_always final_suspend() noexcept {
return {};
}
};
};
Generator2 myCoroutineFunction() {
for(int i = 0; i < 100; ++i) {
co_yield i;
}
}
class Gen{
private:
Generator2 myCoroutineResult;
public:
Gen(){
myCoroutineResult = myCoroutineFunction();
}
int next(){
return (myCoroutineResult.next());
}
};
PYBIND11_MODULE(cmake_example, m) {
pybind11::class_<Gen>(m, "Gen")
.def(pybind11::init())
.def("next", &Gen::next);
}
However I'm getting an error:
Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)
Could c++ coroutines, coroutine_handles, co_yield etc. be a low-level thing that is not supported by PyBind11 yet?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
即使 PyBind11 不直接支持协程,您的问题也不会混合协程和 pybind 代码,因为无论如何您都将协程隐藏在 Gen 后面。
问题是您的
Generator2
类型使用编译器提供的复制和移动构造函数。这一行:
当您调用 myCoroutineFunction 时创建一个协程句柄,并将其放入右侧的临时
Generator2
中。然后,从右侧生成器初始化myCoroutineResult
。一切都很好,但随后临时的东西就被破坏了。您的析构函数检查句柄是否有效:但在您的实现中,成员生成器的
coro
成员从临时对象中复制,而不重置临时对象的coro
成员。因此,一旦初始化myCoroutineResult
,协程本身就会被销毁,并且您将持有一个悬空的协程句柄。请记住,std::coroutine_handle 的行为类似于原始指针。本质上,您违反了规则 5。您有一个自定义析构函数,但没有复制/移动构造函数或赋值运算符。由于无法复制构造协程,因此可以忽略复制构造函数,但需要提供移动构造函数/分配运算符:
此外,使用成员初始化列表来初始化成员,而不是在构造函数体内分配它们。因此,不要这样:
使用这个:
即使在这个答案中也可以看到推理。第一个调用赋值运算符,它执行一系列额外的工作,而第二个调用移动构造函数,它非常精简。
Even though
PyBind11
does not support coroutines directly, your problem does not mix coroutine and pybind code since you are hiding the coroutine behindGen
anyway.The problem is that your
Generator2
type uses the compiler provided copy and move constructors.This line:
Creates a coroutine handle when you call
myCoroutineFunction
, and puts it in the temporaryGenerator2
in the right hand side. Then, you initializemyCoroutineResult
from the right hand side generator. All is well, but then the temporary gets destroyed. Your destructor checks whether the handle is valid or not:But in your implementation, the
coro
member of the member generator gets copied from the temporary without resetting the temporary'scoro
member. So the coroutine itself gets destroyed once you initializemyCoroutineResult
, and you are holding onto a dangling coroutine handle. Remember thatstd::coroutine_handle
s behave like a raw pointer.Essentially, you have a violation of the rule of 5. You have a custom destructor, but no copy/move constructors or assignment operators. Since you cannot copy construct a coroutine, you can ignore the copy constructors but you need to provide move constructors/assigment operators:
Also, use member initialization list to initialize members instead of assigning them within the constructor body. So instead of this:
Use this:
The reasoning can be seen even in this answer. The first one calls the assignment operator, which performs a bunch of additional work, whereas the second one calls the move constructor, which is as lean as it gets.