C++ libraries that involve dynamic runtime polymorphism may choose to use std::unique_ptr to pass instances around. Given a basic type hierarchy like:
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";
}
};
This library may offer functions like these:
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();
}
}
I'm wondering how to write pybind11 bindings for these functions? The obvious approach is:
PYBIND11_MODULE(my_native_module, m)
{
py::class_<Animal>(m, "Animal");
m.def("create_animals", &createAnimals);
m.def("do_something_with_animals", &doSomethingWithAnimals);
}
The binding of createAnimals works fine, and in fact closely follows the pybind11 documentation example on inheritance (see PolymorphicPet).
On the other hand, the binding doSomethingWithAnimals doesn't compile, and actually runs into a rather fundamental limitation, which is documented in the smart pointers section:
While returning unique pointers in this way is allowed, it is illegal to use them as function arguments. [...] The above signature would imply that Python needs to give up ownership of an object that is passed to this function, which is generally not possible.
This somehow makes sense, but there seems to be little value of being able to return an std::vector<std::unique_ptr<T>> in a binding, if this instance cannot be passed back in anywhere.
The section on smart pointers continues to explain that raw pointers and shared pointers are a possible solution. But what if this library is a third party library, or simply cannot be refactored in its entirety to use e.g. raw pointers? Is there no way at all to write pybind11 bindings in such a case?
I guess something dirty like the following doesn't do the job: It now compiles, but it will segfault at runtime, presumably because now the destruction of the unique pointer deletes the instances, but Python still holds references to them...
// Dirty hack, trying to wrap the `doSomethingWithAnimals` call behind a raw pointer interface:
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);
});