pybind11实现元组类__iter__方法的所有权问题及相关咨询
问题描述
我正在为一个元组类编写pybind11包装器,希望为其实现__iter__方法。为此,我将元组的字段转换为variant向量,并按如下方式定义__iter__:
cls.def( "__iter__", [](const MyTuple& t){ // 元编程获取MyTuple的底层类型 using my_variant = std::variant<...>; std::vector<my_variant> fields; // 局部变量! fill(fields); // 填充t的字段并转换为my_variant return py::make_iterator(begin(fields), end(fields)); }, py::keep_alive<0, 1>() );
显然这存在问题,因为局部变量fields会在作用域结束后立即销毁。除了为MyTuple定义封装该向量的自定义holder外,是否有简单技巧可将该辅助向量的所有权移交pybind11?若定义自定义holder,能否从py::class_中访问它?
解决方案
方案1:转换为Python列表返回迭代器(最简单)
直接将局部向量转换为Python内置列表,返回该列表的迭代器。Python的垃圾回收机制会自动管理列表的生命周期,迭代器会依赖列表存活,彻底避免悬空指针问题:
cls.def( "__iter__", [](const MyTuple& t){ using my_variant = std::variant<...>; std::vector<my_variant> fields; fill(fields); // 填充字段 // 将variant向量转换为Python列表 py::list py_fields; for (auto& field : fields) { py_fields.append(std::move(field)); } // 返回列表的迭代器 return py_fields.attr("__iter__")(); } );
方案2:动态分配向量并绑定生命周期
如果不想转换为Python列表,可以用std::shared_ptr动态分配向量,让pybind11接管其所有权,同时通过py::keep_alive将迭代器与向量绑定:
cls.def( "__iter__", [](const MyTuple& t) -> std::pair<py::iterator, std::shared_ptr<std::vector<my_variant>>> { using my_variant = std::variant<...>; auto fields = std::make_shared<std::vector<my_variant>>(); fill(*fields); // 填充字段 auto iter = py::make_iterator(fields->begin(), fields->end()); return {std::move(iter), fields}; }, // 指定迭代器(返回值第0项)持有向量的shared_ptr(返回值第1项) py::keep_alive<0, 1>() );
自定义Holder的实现与访问
如果选择自定义Holder来缓存向量(适合频繁迭代的场景),完全可以在py::class_的方法中访问Holder的成员:
// 自定义Holder,缓存字段向量 template <typename TupleType> struct MyTupleHolder { std::unique_ptr<TupleType> instance; mutable std::optional<std::vector<my_variant>> cached_fields; // 构造函数 explicit MyTupleHolder(std::unique_ptr<TupleType> ptr) : instance(std::move(ptr)) {} }; // 定义类时指定自定义Holder py::class_<MyTuple, MyTupleHolder<MyTuple>>(m, "MyTuple") // 其他方法定义... .def("__iter__", [](const MyTupleHolder<MyTuple>& holder) { // 延迟初始化缓存向量 if (!holder.cached_fields) { holder.cached_fields.emplace(); fill(*holder.cached_fields, *holder.instance); } // 返回迭代器并绑定Holder生命周期 return py::make_iterator(holder.cached_fields->begin(), holder.cached_fields->end()); }, py::keep_alive<0, 1>());
在__iter__的lambda中,直接接收MyTupleHolder<MyTuple>的引用,就可以访问其中的cached_fields缓存向量和元组实例。py::keep_alive<0,1>确保迭代器存活期间,Holder(以及元组实例和缓存向量)不会被销毁。
内容的提问来源于stack exchange,提问作者Igor R.
相关产品推荐
相关产品推荐

