Skip to content

shared_ptr holder constructed but not freed by pybind11 #471

@stukowski

Description

@stukowski

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions