mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-16 13:47:53 +00:00
Track registered instances that share a pointer address
The pointer to the first member of a class instance is the same as the pointer to instance itself; pybind11 has some workarounds for this to not track registered instances that have a registered parent with the same address. This doesn't work everywhere, however: issue #328 is a failure of this for a mutator operator which resolves its argument to the parent rather than the child, as is needed in #328. This commit resolves the issue (and restores tracking of same-address instances) by changing registered_instances from an unordered_map to an unordered_multimap that allows duplicate instances for the same pointer to be recorded, then resolves these differences by checking the type of each matched instance when looking up an instance. (A unordered_multimap seems cleaner for this than a unordered_map<list> or similar because, the vast majority of the time, the instance will be unique).
This commit is contained in:
parent
bb1ee389fd
commit
1b05ce5bc0
@ -9,6 +9,7 @@
|
||||
|
||||
#include "example.h"
|
||||
#include <pybind11/stl.h>
|
||||
#include <pybind11/operators.h>
|
||||
|
||||
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
|
||||
|
||||
@ -157,4 +158,16 @@ void init_issues(py::module &m) {
|
||||
})
|
||||
;
|
||||
|
||||
// Issue #328: first member in a class can't be used in operators
|
||||
#define TRACKERS(CLASS) CLASS() { std::cout << #CLASS "@" << this << " constructor\n"; } \
|
||||
~CLASS() { std::cout << #CLASS "@" << this << " destructor\n"; }
|
||||
struct NestA { int value = 3; NestA& operator+=(int i) { value += i; return *this; } TRACKERS(NestA) };
|
||||
struct NestB { NestA a; int value = 4; NestB& operator-=(int i) { value -= i; return *this; } TRACKERS(NestB) };
|
||||
struct NestC { NestB b; int value = 5; NestC& operator*=(int i) { value *= i; return *this; } TRACKERS(NestC) };
|
||||
py::class_<NestA>(m2, "NestA").def(py::init<>()).def(py::self += int());
|
||||
py::class_<NestB>(m2, "NestB").def(py::init<>()).def(py::self -= int()).def_readwrite("a", &NestB::a);
|
||||
py::class_<NestC>(m2, "NestC").def(py::init<>()).def(py::self *= int()).def_readwrite("b", &NestC::b);
|
||||
m2.def("print_NestA", [](const NestA &a) { std::cout << a.value << std::endl; });
|
||||
m2.def("print_NestB", [](const NestB &b) { std::cout << b.value << std::endl; });
|
||||
m2.def("print_NestC", [](const NestC &c) { std::cout << c.value << std::endl; });
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ from example.issues import ElementList, ElementA, print_element
|
||||
from example.issues import expect_float, expect_int
|
||||
from example.issues import A, call_f
|
||||
from example.issues import StrIssue
|
||||
from example.issues import NestA, NestB, NestC, print_NestA, print_NestB, print_NestC
|
||||
import gc
|
||||
|
||||
print_cchar("const char *")
|
||||
@ -78,3 +79,19 @@ try:
|
||||
print(StrIssue("no", "such", "constructor"))
|
||||
except TypeError as e:
|
||||
print("Failed as expected: " + str(e))
|
||||
|
||||
a = NestA()
|
||||
b = NestB()
|
||||
c = NestC()
|
||||
a += 10
|
||||
b.a += 100
|
||||
c.b.a += 1000
|
||||
b -= 1
|
||||
c.b -= 3
|
||||
c *= 7
|
||||
print_NestA(a)
|
||||
print_NestA(b.a)
|
||||
print_NestA(c.b.a)
|
||||
print_NestB(b)
|
||||
print_NestB(c.b)
|
||||
print_NestC(c)
|
||||
|
@ -24,3 +24,21 @@ Failed as expected: Incompatible constructor arguments. The following argument t
|
||||
1. example.issues.StrIssue(arg0: int)
|
||||
2. example.issues.StrIssue()
|
||||
Invoked with: no, such, constructor
|
||||
NestA@0x1152940 constructor
|
||||
NestA@0x11f9350 constructor
|
||||
NestB@0x11f9350 constructor
|
||||
NestA@0x112d0d0 constructor
|
||||
NestB@0x112d0d0 constructor
|
||||
NestC@0x112d0d0 constructor
|
||||
13
|
||||
103
|
||||
1003
|
||||
3
|
||||
1
|
||||
35
|
||||
NestC@0x112d0d0 destructor
|
||||
NestB@0x112d0d0 destructor
|
||||
NestA@0x112d0d0 destructor
|
||||
NestB@0x11f9350 destructor
|
||||
NestA@0x11f9350 destructor
|
||||
NestA@0x1152940 destructor
|
||||
|
@ -119,12 +119,15 @@ PYBIND11_NOINLINE inline std::string error_string() {
|
||||
return errorString;
|
||||
}
|
||||
|
||||
PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr) {
|
||||
auto instances = get_internals().registered_instances;
|
||||
auto it = instances.find(ptr);
|
||||
if (it == instances.end())
|
||||
return handle();
|
||||
return handle((PyObject *) it->second);
|
||||
PYBIND11_NOINLINE inline handle get_object_handle(const void *ptr, const detail::type_info *type ) {
|
||||
auto &instances = get_internals().registered_instances;
|
||||
auto range = instances.equal_range(ptr);
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
auto instance_type = detail::get_type_info(Py_TYPE(it->second), false);
|
||||
if (instance_type && instance_type == type)
|
||||
return handle((PyObject *) it->second);
|
||||
}
|
||||
return handle();
|
||||
}
|
||||
|
||||
inline PyThreadState *get_thread_state_unchecked() {
|
||||
@ -174,14 +177,7 @@ public:
|
||||
if (src == nullptr)
|
||||
return handle(Py_None).inc_ref();
|
||||
|
||||
// avoid an issue with internal references matching their parent's address
|
||||
bool dont_cache = policy == return_value_policy::reference_internal &&
|
||||
parent && ((instance<void> *) parent.ptr())->value == (void *) src;
|
||||
|
||||
auto& internals = get_internals();
|
||||
auto it_instance = internals.registered_instances.find(src);
|
||||
if (it_instance != internals.registered_instances.end() && !dont_cache)
|
||||
return handle((PyObject *) it_instance->second).inc_ref();
|
||||
auto &internals = get_internals();
|
||||
|
||||
auto it = internals.registered_types_cpp.find(std::type_index(*type_info));
|
||||
if (it == internals.registered_types_cpp.end()) {
|
||||
@ -198,6 +194,14 @@ public:
|
||||
}
|
||||
|
||||
auto tinfo = (const detail::type_info *) it->second;
|
||||
|
||||
auto it_instances = internals.registered_instances.equal_range(src);
|
||||
for (auto it = it_instances.first; it != it_instances.second; ++it) {
|
||||
auto instance_type = detail::get_type_info(Py_TYPE(it->second), false);
|
||||
if (instance_type && instance_type == tinfo)
|
||||
return handle((PyObject *) it->second).inc_ref();
|
||||
}
|
||||
|
||||
object inst(PyType_GenericAlloc(tinfo->type, 0), false);
|
||||
|
||||
auto wrapper = (instance<void> *) inst.ptr();
|
||||
@ -229,8 +233,8 @@ public:
|
||||
}
|
||||
|
||||
tinfo->init_holder(inst.ptr(), existing_holder);
|
||||
if (!dont_cache)
|
||||
internals.registered_instances[wrapper->value] = inst.ptr();
|
||||
|
||||
internals.registered_instances.emplace(wrapper->value, inst.ptr());
|
||||
|
||||
return inst.release();
|
||||
}
|
||||
|
@ -266,9 +266,9 @@ struct overload_hash {
|
||||
|
||||
/// Internal data struture used to track registered instances and types
|
||||
struct internals {
|
||||
std::unordered_map<std::type_index, void*> registered_types_cpp; // std::type_index -> type_info
|
||||
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
|
||||
std::unordered_map<const void *, void*> registered_instances; // void * -> PyObject*
|
||||
std::unordered_map<std::type_index, void*> registered_types_cpp; // std::type_index -> type_info
|
||||
std::unordered_map<const void *, void*> registered_types_py; // PyTypeObject* -> type_info
|
||||
std::unordered_multimap<const void *, void*> registered_instances; // void * -> PyObject*
|
||||
std::unordered_set<std::pair<const PyObject *, const char *>, overload_hash> inactive_overload_cache;
|
||||
std::forward_list<void (*) (std::exception_ptr)> registered_exception_translators;
|
||||
#if defined(WITH_THREAD)
|
||||
|
@ -731,20 +731,26 @@ protected:
|
||||
self->owned = true;
|
||||
self->parent = nullptr;
|
||||
self->constructed = false;
|
||||
detail::get_internals().registered_instances[self->value] = (PyObject *) self;
|
||||
detail::get_internals().registered_instances.emplace(self->value, (PyObject *) self);
|
||||
return (PyObject *) self;
|
||||
}
|
||||
|
||||
static void dealloc(instance<void> *self) {
|
||||
if (self->value) {
|
||||
bool dont_cache = self->parent && ((instance<void> *) self->parent)->value == self->value;
|
||||
if (!dont_cache) { // avoid an issue with internal references matching their parent's address
|
||||
auto ®istered_instances = detail::get_internals().registered_instances;
|
||||
auto it = registered_instances.find(self->value);
|
||||
if (it == registered_instances.end())
|
||||
pybind11_fail("generic_type::dealloc(): Tried to deallocate unregistered instance!");
|
||||
registered_instances.erase(it);
|
||||
auto instance_type = Py_TYPE(self);
|
||||
auto ®istered_instances = detail::get_internals().registered_instances;
|
||||
auto range = registered_instances.equal_range(self->value);
|
||||
bool found = false;
|
||||
for (auto it = range.first; it != range.second; ++it) {
|
||||
if (instance_type == Py_TYPE(it->second)) {
|
||||
registered_instances.erase(it);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
pybind11_fail("generic_type::dealloc(): Tried to deallocate unregistered instance!");
|
||||
|
||||
Py_XDECREF(self->parent);
|
||||
if (self->weakrefs)
|
||||
PyObject_ClearWeakRefs((PyObject *) self);
|
||||
@ -1316,8 +1322,8 @@ class gil_scoped_acquire { };
|
||||
class gil_scoped_release { };
|
||||
#endif
|
||||
|
||||
inline function get_overload(const void *this_ptr, const char *name) {
|
||||
handle py_object = detail::get_object_handle(this_ptr);
|
||||
inline function get_type_overload(const void *this_ptr, const detail::type_info *this_type, const char *name) {
|
||||
handle py_object = detail::get_object_handle(this_ptr, this_type);
|
||||
if (!py_object)
|
||||
return function();
|
||||
handle type = py_object.get_type();
|
||||
@ -1348,6 +1354,14 @@ inline function get_overload(const void *this_ptr, const char *name) {
|
||||
return overload;
|
||||
}
|
||||
|
||||
template <class T> function get_overload(const T *this_ptr, const char *name) {
|
||||
auto &cpp_types = detail::get_internals().registered_types_cpp;
|
||||
auto it = cpp_types.find(typeid(T));
|
||||
if (it == cpp_types.end())
|
||||
return function();
|
||||
return get_type_overload(this_ptr, (const detail::type_info *) it->second, name);
|
||||
}
|
||||
|
||||
#define PYBIND11_OVERLOAD_INT(ret_type, name, ...) { \
|
||||
pybind11::gil_scoped_acquire gil; \
|
||||
pybind11::function overload = pybind11::get_overload(this, name); \
|
||||
|
Loading…
Reference in New Issue
Block a user