如何在pybind11中处理要求std::unique_ptr作为输入的C++库?
问题:为接收std::unique_ptr容器的C++函数编写pybind11绑定
基础类型与库函数定义
给定包含动态多态的C++类型层级:
struct Animal { virtual ~Animal() = default; virtual std::string makeSound() const = 0; }; struct Dog : public Animal { std::string makeSound() const { return "woof"; } }; struct Cat : public Animal { std::string makeSound() const { return "meow"; } };
库提供以下函数:
std::vector<std::unique_ptr<Animal>> createAnimals() { std::vector<std::unique_ptr<Animal>> animals; animals.emplace_back(std::make_unique<Dog>()); animals.emplace_back(std::make_unique<Cat>()); return animals; } void doSomethingWithAnimals(const std::vector<std::unique_ptr<Animal>>& animals) { for (const auto& animal : animals) { animal->makeSound(); } }
常规绑定的问题
常规的pybind11绑定代码如下:
PYBIND11_MODULE(my_native_module, m) { py::class_<Animal>(m, "Animal"); m.def("create_animals", &createAnimals); m.def("do_something_with_animals", &doSomethingWithAnimals); }
其中create_animals可以正常工作,但do_something_with_animals无法编译。这是pybind11的固有限制,文档中说明:
虽然允许以这种方式返回unique_ptr,但将其用作函数参数是不合法的。[...]上述签名意味着Python需要放弃传递给该函数的对象所有权,而这通常是不可能的。
如果无法修改第三方库的接口,需要寻找可行的绑定方案。
不规范写法的风险
尝试直接将裸指针转为unique_ptr的写法可以编译,但运行时会触发段错误:
// 存在风险的写法,运行时会段错误 m.def("do_something_with_animals_dangerous", [](const std::vector<Animal*>& animals) { std::vector<std::unique_ptr<Animal>> animals_unique_ptrs; for (const auto raw_ptr : animals) { animals_unique_ptrs.push_back(std::unique_ptr<Animal>(raw_ptr)); } doSomethingWithAnimals(animals_unique_ptrs); });
原因是当临时animals_unique_ptrs销毁时,内部的unique_ptr会删除对应的Animal实例,但Python端仍持有该实例的引用,后续访问会触发非法内存访问。
可行解决方案
核心思路是:构造std::unique_ptr时使用空删除器,避免其销毁时释放Python仍拥有所有权的对象。通过lambda包装函数,接收Python列表并转换为符合要求的std::vector<std::unique_ptr<Animal>>:
PYBIND11_MODULE(my_native_module, m) { py::class_<Animal>(m, "Animal"); py::class_<Dog, Animal>(m, "Dog"); py::class_<Cat, Animal>(m, "Cat"); m.def("create_animals", &createAnimals); m.def("do_something_with_animals", [](const py::list& animals_list) { std::vector<std::unique_ptr<Animal>> animals; for (const auto& py_animal : animals_list) { // 获取裸指针,并用空删除器构造unique_ptr,不释放对象 Animal* raw_ptr = py_animal.cast<Animal*>(); animals.emplace_back(raw_ptr, [](Animal*) {}); } doSomethingWithAnimals(animals); }); }
关键说明
- 必须显式绑定派生类
Dog和Cat,否则pybind11无法正确识别多态类型。 - 使用空删除器
[](Animal*) {}的unique_ptr,在容器销毁时不会调用delete,避免和Python的所有权管理冲突。 - 由于
doSomethingWithAnimals仅接收const引用,不会修改容器或转移所有权,因此这种写法是安全的。
内容的提问来源于stack exchange,提问作者bluenote10
相关产品推荐
相关产品推荐

