在C ++中增加pybind11句柄的ref_count

问题描述

我有一个虚拟对象,我使用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