I have a similar problem as in this mail. I want that every time I return the same C++ managed object to Python, I get the same PyObject
. With the standard converter I get always a new wrapper-PyObject
. The solution should fulfill the following requirements:
get() == get()
(the Python object is always equal, whatever function returns the same C++ object)get()
isget()
- The same is valid for any objects returned in containers (which the solution from the mail lacks)
getArray()[0] == get()
- I can instance attributes (
get().new_var = "xx"
->get().new_var == "xx"
)
I wrote a template converter which takes as input a shared_ptr
. It looks in a static map, if this shared_ptr
is already returned, than it returns the existing PyObject
, else a new one is created. I haven't found a solution there I can automatically remove unused objects.
In this case, I added a function to manually remove an entry. This works because the object is managed in C++ and I can intercept than the object should be removed from the C++ side. If Python has still a reference to this object, it's ok to wait for the GC to collect the Python side and implicitly remove the C++ object. If the last reference is removed in Python, the PyObject
is still valid, because the map holds still a reference. Also , as long as the PyObject
inside the map exists, the C++ object cannot be removed from the heap.
I made a couple of test, which shows the validity, that can be found here.
The usage is:
boost::python::to_python_converter<boost::shared_ptr<A>,
c_ptr_to_cached_python<A>>();
Is the following code enough to fulfill the requirements for any C++ class wrapped in a shared_ptr
? Is the PyObject
generation valid and can it be improved?
#pragma once
#include <boost/python.hpp>
#include <map>
#include <iostream>
template<typename C_CLASS>
using storage_map = std::map<C_CLASS*, PyObject*>;
template<typename C_CLASS>
storage_map<C_CLASS>* getCachedObjects() {
static storage_map<C_CLASS> *storage = new storage_map<C_CLASS>();
return storage;
}
template<typename C_CLASS>
using c_sptr = boost::shared_ptr<C_CLASS>;
template<typename C_CLASS>
using py_ptr = typename boost::python::pointee<boost::shared_ptr<C_CLASS>>::type;
template<typename C_CLASS>
PyObject * createObject(const c_sptr<C_CLASS>& obj) {
return boost::python::objects::make_ptr_instance<py_ptr<C_CLASS>,
boost::python::objects::pointer_holder<c_sptr<C_CLASS>,
py_ptr<C_CLASS>>>::execute(obj);
}
template<typename C_CLASS>
void cleanMap() {
auto map = getCachedObjects<C_CLASS>();
for (auto it = map->begin(); it != map->end();) {
auto py_obj = it->second;
Py_DecRef(py_obj);
map->erase(it++);
}
}
template<typename C_CLASS>
void removeObject(const c_sptr<C_CLASS>& obj) {
auto map = getCachedObjects<C_CLASS>();
auto raw_ptr = obj.get();
auto it = map->find(raw_ptr);
if (it == map->end()) {
throw std::logic_error("Object was already removed.");
}
auto py_obj = it->second;
Py_DecRef(py_obj);
map->erase(it);
}
template<typename C_CLASS>
struct c_ptr_to_cached_python {
static PyObject* convert(const c_sptr<C_CLASS> &obj) {
auto raw_ptr = obj.get();
auto map = getCachedObjects<C_CLASS>();
auto it = map->find(raw_ptr);
if (it == map->end()) {
auto py_obj = createObject<C_CLASS>(obj);
(*map)[raw_ptr] = py_obj;
Py_INCREF(py_obj); // Increment to store in map
return py_obj;
} else {
auto old_py_obj = it->second;
Py_INCREF(old_py_obj);
return old_py_obj;
}
}
};