You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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);
  });
}

关键说明

  1. 必须显式绑定派生类Dog和Cat,否则pybind11无法正确识别多态类型。
  2. 使用空删除器[](Animal*) {}的unique_ptr,在容器销毁时不会调用delete,避免和Python的所有权管理冲突。
  3. 由于doSomethingWithAnimals仅接收const引用,不会修改容器或转移所有权,因此这种写法是安全的。

内容的提问来源于stack exchange,提问作者bluenote10

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 02:13:16