-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Description
Thank you for making PyBind11!
I have a relatively complex ("externally given") class with a funny ownership model, so things can go missing while Python has a reference. To wrap it in Python safely, I'm trying to hold instances in a smart pointer (a la shared_ptr
) that checks on dereferencing whether the referred to object still exists. It does so by another pointer indirection.
The trouble is that while PyBind will create the shared ptr, I cannot seem to enforce that argument conversion always goes through the smart ptr and not the raw ptr held alongside.
I tried to do a custom caster, but that ran into "specialization after instantiation" error messages.
There is an "obvious path" to go through lambdas that all take the smart pointer and dereference themselves, but this is obviously a bit cumbersome when it needs to be done to a large number of methods.
@YannickJadoul looked at this on gitter to help clarify my thoughts (but all errors are mine). Thank you Yannick!
#include <pybind11/pybind11.h>
#include <iostream>
template <typename T>
struct Wrap {
Wrap(T* p) : elem(p) {}
void clear() { elem = nullptr; }
T* elem;
};
class MyClass {
public:
MyClass() : wrap_(std::make_shared<Wrap<MyClass>>(this)) {
}
std::shared_ptr<Wrap<MyClass>> wrap() {
return wrap_;
}
~MyClass() {
wrap_->clear();
}
private:
std::shared_ptr<Wrap<MyClass>> wrap_;
};
template <typename T>
class unwrapping_shared_ptr {
private:
std::shared_ptr<Wrap<T>> impl;
public:
unwrapping_shared_ptr() = default;
unwrapping_shared_ptr(std::shared_ptr<T> p) : impl(p) { std::cerr << "construct shared_ptr\n"; }
unwrapping_shared_ptr(T* p) : impl(p->wrap()) { std::cerr << "construct T*\n"; }
T* get() const {
if (! impl->elem) {
throw std::logic_error("has been invalidated");
}
std::cerr << "get deref!\n";
return impl->elem;
}
T** operator&() {
if (! impl->elem) {
throw std::logic_error("has been invalidated");
}
std::cerr << "& deref!\n";
return &(impl->elem);
}
};
PYBIND11_DECLARE_HOLDER_TYPE(T, unwrapping_shared_ptr<T>, true);
namespace py = pybind11;
PYBIND11_MODULE(test_ext, m) {
py::class_<MyClass, unwrapping_shared_ptr<MyClass>>(m, "MyClass")
.def("__str__", [] (MyClass& m) { return "MyClass - jolly good"; });
m.def("get_something", [] () { return new MyClass(); });
m.def("delete_something", [] (MyClass* m) { delete m; });
m.def("test_something", [] (unwrapping_shared_ptr<MyClass> m) {
if (m.get()) {
return "alive";
} else {
return "just resting";
}});
}
and then in Python this gives me:
In [1]: import test_ext
In [2]: t = test_ext.get_something()
construct T*
In [4]: test_ext.test_something(t)
get deref!
Out[4]: 'alive'
In [5]: test_ext.delete_something(t)
In [6]: test_ext.test_something(t)
---------------------------------------------------------------------------
RuntimeError Traceback (most recent call last)
<ipython-input-6-34740f9db26d> in <module>
----> 1 test_ext.test_something(t)
RuntimeError: has been invalidated
In [7]: str(t)
Out[7]: 'MyClass - jolly good'
In [8]:
Ideally, in [7]
it would try to dereference the smart pointer, but it does not.