[FEAT] being able to enforce the dereferencing of the smart pointer in argument conversion
Nobody has claimed this yet.
- Dominant language
- C++
- Stars
- 18k
- Forks
- 2.3k
- Avg merge
- 5d 17h
- Merged PRs (30d)
- 10
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the PYBIND11_DECLARE_HOLDER_TYPE declaration and the py::class_ and test_something entry points shown in the example. Trace how argument conversion selects the raw pointer versus unwrapping_shared_ptr, then determine how the requested dereferencing behavior should apply to methods such as str. Done means the smart pointer's invalidation check is consistently enforced during conversion without requiring per-method lambdas.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp, python
- Domain
- api
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100