10. 智能指针

10.1 std::unique_ptr

给定一个带Python绑定的类Example,我们可以像下面一样返回它的unique pointer智能指针实例:

  1. std::unique_ptr<Example> create_example() { return std::unique_ptr<Example>(new Example()); }
  2. m.def("create_example", &create_example);

没其他地方需特殊处理的。但是需要注意的是,虽然允许返回unique_ptr对象,但是将其作为函数入参是非法的。例如,pybind11不能处理下列函数签名。

  1. void do_something_with_example(std::unique_ptr<Example> ex) { ... }

上面的签名意味着Python需要放弃对象的所有权,并将其传递给该函数,这通常是不可能的(对象可能在别处被引用)。

10.2 std::shared_ptr

class_可以传递一个表示持有者类型的模板类型,它用于管理对象的引用。在不指定的情况下,默认为std::unique_ptr<Type>类型,这意味着当Python的引用计数为0时,将析构对象。该模板类型可以指定为其他的智能指针或引用计数包装类,像下面我们就使用了std::shared_ptr

  1. py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example");

注意,每个类仅能与一个持有者类型关联。

使用持有者类型的一个潜在地障碍就是,你需要始终如一的使用它们。你猜猜下面的绑定代码有什么问题?

  1. class Child { };
  2. class Parent {
  3. public:
  4. Parent() : child(std::make_shared<Child>()) { }
  5. Child *get_child() { return child.get(); } /* Hint: ** DON'T DO THIS ** */
  6. private:
  7. std::shared_ptr<Child> child;
  8. };
  9. PYBIND11_MODULE(example, m) {
  10. py::class_<Child, std::shared_ptr<Child>>(m, "Child");
  11. py::class_<Parent, std::shared_ptr<Parent>>(m, "Parent")
  12. .def(py::init<>())
  13. .def("get_child", &Parent::get_child);
  14. }

下面的Python代码将导致未定义行为(类似段错误)。

  1. from example import Parent
  2. print(Parent().get_child())

问题在于Parent::get_child()返回类Child实例的指针,但事实上这个经由std::shared_ptr<...>管理的实例,在传递原始指针时就丢失了。这个例子中,pybind11将创建第二个独立的std::shared_ptr<...>申明指针的所有权。最后,对象将被free两次,因为两个shared指针没法知道彼此的存在。

有两种方法解决这个问题:

  1. 对于智能指针管理的类型,永远不要在函数如参数或返回值中使用原始指针。换句话说,在任何需要使用该类型指针的地方,使用它们指定的持有者类型代替。这个例子中get_child()可以这样修改:

    1. std::shared_ptr<Child> get_child() { return child; }
  2. 定义Child时指定std::enable_shared_from_this<T>作为基类。这将在Child的基础上增加一点信息,让pybind11认识到这里已经存在一个std::shared_ptr<...>,并与之交互。修改示例如下:

    1. class Child : public std::enable_shared_from_this<Child> { };

10.3 自定义智能指针

pybind11支持开箱即用的 std::unique_ptrstd::shared_ptr 。对于其他自定义的智能指针,可以使用下面的宏使能透明转换(transparent conversions)。它必须在其他绑定代码之前在顶层名称空间中声明:

  1. PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);

宏的第一个参数为占位符名称,用作第二个参数的模板参数。因此,你可以使用任意的标识符(不要使用你的代码中已经存在的类型),只需保持两边一致即可。

宏也可以接收第三个可选的bool类型参数,默认为false。

  1. PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>, true);

如果SmartPtr<T>总是从T*指针初始化,不存在不一致的风险(如多个独立的SmartPtr<T>认为他们是T*指针的唯一拥有者)。当T实例使用侵入式引用计数时,应设定为true

在使用该特性前,请先阅读 General notes regarding convenience macros

默认情况下,pybind11假定自定义智能指针具有标准接口,如提供.get()成员函数来获取底层的原始指针。如果没有,则需要指定holder_helper

  1. // Always needed for custom holder types
  2. PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
  3. // Only needed if the type's `.get()` goes by another name
  4. namespace pybind11 { namespace detail {
  5. template <typename T>
  6. struct holder_helper<SmartPtr<T>> { // <-- specialization
  7. static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }
  8. };
  9. }}

上述特化告诉pybind11,自定义SmartPtr通过.getPointer()提供.get()接口。

see also: The file tests/test_smart_ptr.cpp contains a complete example that demonstrates how to work with custom reference-counting holder types in more detail.