-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
C++ ownership without shared_ptr #1150
Comments
You're going to have to use py::class_<a>(m, "a")
.def(py::init<>());
py::class_<b>(m, "b")
.def(py::init<>())
.def_property("ptrToA",
[](b &self) { return self.ptrToA; },
py::cpp_function([](b &self, a *aptr) { self.ptrToA = aptr; }, py::keep_alive<1, 2>())
); |
Thanks for the information. Your fix works for the trivial example I posted (though I'm a little curious, reading through the header I thought all the "extra" arguments were applied to the generated getter/setter functions when using def_readwrite). For some reason, the fix doesn't work for the code I'm actually binding and I'm trying to figure out what's different. My pointer is a base class pointer and the object is a derived class, maybe that's the problem? I will see if I can build up a simple example of the issue I'm facing later today. |
For more complicated cases, you're really hurting yourself by trying to avoid If class PyB : public B {
public:
using B::B;
std::shared_ptr<A> a;
};
py::class_<A, std::shared_ptr<A>>(m, "A")
.def(py::init<>());
py::class_<B, PyB>(m, "B")
.def(py::init_alias<>()) // default constructor
//.def(py::init_alias<int, std::string>()) // some other constructor
.def_property("a", [](PyB &self) { return self.a; },
[](PyB &self, std::shared_ptr<A> new_a) {
self.a = std::move(new_a);
self.aptr = self.a.get();
})
; |
A third option is similar to the above, but instead of storing a [](PyB &self, py::object new_a) {
self.aptr = new_a.cast<A *>();
self.a = std::move(new_a);
} This basically does the same thing as the Note also that both of these approaches require |
Excellent information, thank you. So, admittedly it's an ill-formed problem. We want Python to assume ownership but c++ to avoid it. My big concern is passing this code off to the next developer, where they try to create an a and a b as I did in my example, and end up with a hard to debug segfault for what seems to be a straightforward operation. In a c++ world it's somewhat easy to grasp the lifetime issue of creating a class, passing it to another class, and having to be sure to keep it alive until that class no longer needs it, but because Python obscures that, it's a burden to put that on future developers. |
I'm still playing with code but for future struggling devs stumbling on this page, I will warn you that the keep_alive<>() solution does appear to have the pitfall of not being able to sever the link if the pointer is re-assigned. Using a slightly modified version of the code above that added a c subclass of a:
|
When testing destruction triggered from python code, you'd do well to add |
Just to add in on Jason's statements, and given these statements in your above post, it seems like simply using The caveat, as you hinted at before, is that you know you have to shoehorn your object lifetimes into Wanting C++ to not strictly own the object (in the AFAICT, there isn't going to be another solution that doesn't involve some sort of shared-ownership mechanism, whether you store a (If you do think you have something robust, though, do let us know!) |
I think the main takeaway of my post is that I wish there were an "ownership transfer" I could identify, where C++ used a weak_ptr interface and Python used a shared_ptr interface (that could ideally exist without modifying the original source). SIP bindings, which I used prior to pybind11, allowed notation to indicate whether you wanted Python or C++ to be responsible for an object, but I assume that was just a benefit to using a language that had its own parser versus trying to use built in language semantics. For the time being, we hacked in a somewhat ugly workaround where we created a "PyPtrWrapper" class, which was returned via the property getter in place of binding the actual pointer. That wrapper class had a static vector of py::object, and we just copied the provided object on set into that array. On set, we also cast the old value back to a py::object and removed an instance of that object from the vector if found, so repeated assignments don't cause memory leaks (though it is not smart enough to clean up if the object is destroyed, fortunately that's not a normal use case for us). |
This issue seems resolved. |
I have a simple class that expects a pointer to another class, both of which are bound and instantiated in Python.
Python:
Running the Python shows a destructor call immediately after the object is constructed, so it appears c++ is only generating a weakref to the instance of A. I don't necessarily have the luxury of converting the actual code to use smart pointers which seems to be what the docs mostly cover, so is there an easy way in the binding code to specify that Python should keep the constructed a() alive while b (or really, b.ptrToA) is alive? I thought keep_alive was the answer I was looking for, but that did not seem to fix the issue (though admittedly, I may have gotten the usage wrong).
I mention ownership in the title, but maybe that's not quite the right word. I want Python to recognize the reference exists until B is destroyed, not to require c++ to delete ptrToA when done with it.
The text was updated successfully, but these errors were encountered: