-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
I think I have found a problem with the construction/destruction of smart pointer holders. It seems like pybind11 does not always destruct the smart pointer holders it creates, because it thinks it doesn't "own" them.
Here is a small code snippet that demonstrates that behavior. It registers a class that uses std::shared_ptr
as holder type. As suggested by the pybind11 documentation, std::enable_shared_from_this
is used so that shared pointers and raw pointers can be used interchangeably.
class MyClass : public std::enable_shared_from_this<MyClass> {};
py::class_<MyClass, std::shared_ptr<MyClass>>(m, "MyClass");
std::shared_ptr<MyClass> instance = std::make_shared<MyClass>();
std::cout << instance.use_count() << std::endl; // Prints "1", that's the shared_ptr instance above.
py::object o = py::cast(instance.get()); // Note we are passing a raw pointer to cast()
std::cout << instance.use_count() << std::endl; // Prints "2", we now have a 2nd shared_ptr (the holder)
o = py::none(); // Delete Python variable
std::cout << instance.use_count() << std::endl; // Prints "2" (This seems wrong! The holder should have been destroyed)
Note that cast()
assumes return_value_policy::reference
, because we are passing it a raw pointer. Even though a new shared_ptr
holder is constructed by pybind11 as part of the cast, the wrapper->owned
field is set to false
by type_caster_generic::cast()
. This prevents the internal shared_ptr
from getting freed when the wrapper is destroyed. A dangling reference to the C++ class instance remains.
The above problem can be worked around either by passing the shared_ptr
to cast()
, not the raw pointer. Or by explicitly passing return_value_policy::take_ownership
to cast()
as second argument.
However, my C++ interface that I am trying to wrap with pybind11 typically returns raw pointers and relies on the std::enable_shared_from_this
mechanism. This allows constructing reference counted pointers from raw pointers only when needed. That means I need cast()
to behave the same for shared pointers and raw pointers. In both cases a shared_ptr
holder should be constructed and, when the Python wrapper goes out of scope, destructed again.