C++ object is destructed before it can be used, when returned as a shared_ptr and using default holder type · Issue #1215 · pybind/pybind11 (original) (raw)

Issue description

When returning a shared_ptr to a bound object from a C++ function, the object seems to be immediately destructed, although it continues to be "usable" from Python, but this is dangerous, because it is a "dangling" object.

What I think would be more appropriate in this case:

  1. Give an error message stating that a function may not return shared_ptr of an object when it's holder type is the default one.
  2. If that is not possible, at least let the function return None, this is way less dangerous.

Note that the object ends up being destructed twice!

Reproducible example code

#include <pybind11/pybind11.h> #include #include

namespace py = pybind11;

class BindingsTest { public: static size_t constructor_called; static size_t destructor_called;

BindingsTest() { constructor_called += 1; status = "OK";  }
~BindingsTest() { destructor_called += 1; status = "INVALID"; }
BindingsTest(BindingsTest const&) = delete;
const char* get_status() const { return status; }

protected: const char* status; bool destructor_already_called; };

size_t BindingsTest::constructor_called = 0; size_t BindingsTest::destructor_called = 0;

std::shared_ptr create_bindings_test() { return std::make_shared(); }

size_t get_constructor_called() { return BindingsTest::constructor_called; } size_t get_destructor_called() { return BindingsTest::destructor_called; } void reset_called() { BindingsTest::constructor_called = 0; BindingsTest::destructor_called = 0; }

PYBIND11_MODULE(_pb11pg, m) { m.doc() = "pybind11 testing module";

py::class_<BindingsTest>(m, "BindingsTest")
    .def("get_status", &BindingsTest::get_status);
m.def("create_bindings_test", &create_bindings_test);
m.def("reset_called", &reset_called);
m.def("get_constructor_called", &get_constructor_called);
m.def("get_destructor_called", &get_destructor_called);

}

def test_bindings_problem(): import _pb11pg _pb11pg.reset_called()

x = _pb11pg.create_bindings_test()

# Destructor was already called here
assert x.get_status() == 'OK'
assert _pb11pg.get_constructor_called() == 1
assert _pb11pg.get_destructor_called() == 0

x = None
assert _pb11pg.get_constructor_called() == 1
assert _pb11pg.get_destructor_called() == 1

Workaround

Use shared_ptr as a holder type, but the problem is that it is very hard to find that this is the issue causing the segfaults and unexpected behaviours:

py::class_<BindingsTest, shared_ptr<BindingsTest>> instead of py::class_<BindingsTest>