10. 智能指针
10.1 std::unique_ptr
给定一个带Python绑定的类Example
,我们可以像下面一样返回它的unique pointer智能指针实例:
std::unique_ptr<Example> create_example() { return std::unique_ptr<Example>(new Example()); }
m.def("create_example", &create_example);
没其他需要特殊处理的地方。需要注意的是,虽然允许返回unique_ptr对象,但是将其作为函数入参是非法的。例如,pybind11不能处理下列函数签名。
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
:
py::class_<Example, std::shared_ptr<Example> /* <- holder type */> obj(m, "Example");
注意,每个类仅能与一个持有者类型关联。
使用持有者类型的一个潜在的障碍就是,你需要始终如一的使用它们。猜猜下面的绑定代码有什么问题?
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);
}
下面的Python代码将导致未定义行为(类似段错误)。
from example import Parent
print(Parent().get_child())
问题在于Parent::get_child()
返回类Child
实例的指针,但事实上这个经由std::shared_ptr<...>
管理的实例,在传递原始指针时就丢失了。这个例子中,pybind11将创建第二个独立的std::shared_ptr<...>
声明指针的所有权。最后,对象将被free两次,因为两个shared指针没法知道彼此的存在。
有两种方法解决这个问题:
-
对于智能指针管理的类型,永远不要在函数如参数或返回值中使用原始指针。换句话说,在任何需要使用该类型指针的地方,使用它们指定的持有者类型代替。这个例子中
get_child()
可以这样修改:std::shared_ptr<Child> get_child() { return child; }
-
定义
Child
时指定std::enable_shared_from_this<T>
作为基类。这将在Child
的基础上增加一点信息,让pybind11认识到这里已经存在一个std::shared_ptr<...>
,并与之交互。修改示例如下:class Child : public std::enable_shared_from_this<Child> { };
10.3 自定义智能指针
pybind11支持开箱即用的 std::unique_ptr
和 std::shared_ptr
。对于其他自定义的智能指针,可以使用下面的宏使能透明转换(transparent conversions)。它必须在其他绑定代码之前在顶层名称空间中声明:
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
宏的第一个参数为占位符名称,用作第二个参数的模板参数。因此,你可以使用任意的标识符(不要使用你的代码中已经存在的类型),只需保持两边一致即可。
宏也可以接收第三个可选的bool类型参数,默认为false。
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
:
// Always needed for custom holder types
PYBIND11_DECLARE_HOLDER_TYPE(T, SmartPtr<T>);
// Only needed if the type's `.get()` goes by another name
namespace pybind11 { namespace detail {
template <typename T>
struct holder_helper<SmartPtr<T>> { // <-- specialization
static const T *get(const SmartPtr<T> &p) { return p.getPointer(); }
};
}}
上述特化告诉pybind11,自定义SmartPtr
通过.getPointer()
提供.get()
接口。
see also: 文件
tests/test_smart_ptr.cpp
提供了一个展示如何使用自定义引用计数holder类型的详细示例。