问题描述
我有一个虚拟对象,我使用pybind11在python中子类化并实现了该对象,然后将该对象用于C ++代码,但是当python脚本完成并且python垃圾收集器破坏了我的对象之前,python对象超出了范围在C ++端使用它。
为了避免破坏,我在绑定代码中添加了一个“ keep”方法来增加ref_count,以便python垃圾收集器不会破坏它。
绑定的C ++代码
VNCS::Real VNCS::py::BlendingField::blending(const VNCS::Point_3 &p) const
{
std::array<double,3> pyP{p[0],p[1],p[2]};
PYBIND11_OVERLOAD_PURE(VNCS::Real,/* Return type */
VNCS::BlendingField,/* Parent class */
blending,/* Name of function in C++ (must match Python name) */
pyP /* Argument(s) */
);
}
void VNCS::py::module::blendingField(pybind11::module &m)
{
pybind11::class_<VNCS::BlendingField,VNCS::py::BlendingField,std::shared_ptr<VNCS::BlendingField>>(
m,"BlendingField")
.def(pybind11::init(
[](pybind11::args &args,pybind11::kwargs &kwargs) { return std::make_shared<VNCS::py::BlendingField>(); }))
.def("keep",[](pybind11::handle handle) { handle.inc_ref(); });
}
Python代码
class BlendingField(PyVNCS.BlendingField):
def __init__(self,*args,**kwargs):
PyVNCS.BlendingField.__init__(self,**kwargs)
def __del__(self):
print("dtor from python")
def blending(self,point):
return point[0]
blendingField = BlendingField()
blendingField.keep()
simCreator = initObject.addobject("SimCreator",name="simCreator",coarseMesh="@lowResTopology",detailMesh="@highResTopology")
simCreator.setBlendingField(blendingField)
这看起来不太好,因为那里的keep
方法感觉很糟糕。正确的方法是什么?
解决方法
我认为弄乱引用计数不是最好的解决方案。如果要在C ++领域使用对象,请使用C ++寿命管理工具。
Pybind将生命周期管理集成到语言绑定中,您只需要更改其行为即可。引用docs:
可以为类
<vue-editor v-model="restInfo[section]"
的绑定生成器传递一个模板类型,该模板类型表示一种特殊的持有人类型,用于管理对该对象的引用。如果未提供此类持有人类型模板参数,则名为Type的类型的默认值为class_
,这意味着当Python的引用计数为零时,将释放该对象。可以切换到其他类型的引用计数包装器或智能指针,这在依赖它们的代码库中很有用。例如,以下代码片段导致使用
std::unique_ptr<Type>
。
以下是从tests引出的示例:
std::shared_ptr
如果您按照我的建议去做,那这个问题将是this one的重复。
请注意,这里有一些小缺陷,尤其是当您的代码库在python代码周围使用原始指针时。如果从python调用返回原始指针指向共享对象的C ++方法,则文档指向可能的双重释放。再次从docs窃取:
// Object managed by a std::shared_ptr<>
class MyObject2 {
public:
MyObject2(const MyObject2 &) = default;
MyObject2(int value) : value(value) { print_created(this,toString()); }
std::string toString() const { return "MyObject2[" + std::to_string(value) + "]"; }
virtual ~MyObject2() { print_destroyed(this); }
private:
int value;
};
py::class_<MyObject2,std::shared_ptr<MyObject2>>(m,"MyObject2")
.def(py::init<int>());
m.def("make_myobject2_1",[]() { return new MyObject2(6); });
m.def("make_myobject2_2",[]() { return std::make_shared<MyObject2>(7); });
m.def("print_myobject2_1",[](const MyObject2 *obj) { py::print(obj->toString()); });
m.def("print_myobject2_2",[](std::shared_ptr<MyObject2> obj) { py::print(obj->toString()); });
m.def("print_myobject2_3",[](const std::shared_ptr<MyObject2> &obj) { py::print(obj->toString()); });
m.def("print_myobject2_4",[](const std::shared_ptr<MyObject2> *obj) { py::print((*obj)->toString()); });
class Child { };
class Parent {
public:
Parent() : child(std::make_shared<Child>()) { }
Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */
private:
std::shared_ptr<Child> child;
};
PYBIND11_MODULE(example,m) {
py::class_<Child,std::shared_ptr<Child>>(m,"Child");
py::class_<Parent,std::shared_ptr<Parent>>(m,"Parent")
.def(py::init<>())
.def("get_child",&Parent::get_child);
}
尽管这些问题在我看来似乎不是from example import Parent
print(Parent().get_child()) // UB or Segfault
所独有,而不是shared_ptr
。