Skip to content

Commit eac1ce2

Browse files
committed
Expose more instance management functions
This breaks up the instance management functions in class_support.h a little bit so that other pybind11 code can use it. In particular: - added make_new_instance() which does what pybind11_object_new does, but also allows instance allocation without `value` allocation. This lets `cast.h` use the same instance allocation rather than having its own separate implementation. - instance registration is now moved to a `register_instance()`/deregister_instance()` pair (rather than having individual code add or remove things from `registered_instances` directory). - clear_instance() does everything `pybind11_object_dealloc()` needs except for the deallocation; this is helpful for factory construction which needs to be able to replace the internals of an instance without deallocating it. - clear_instance() now also calls `dealloc` when `holder_constructed` is true, even if `value` is false. This can happen in factory construction when the pointer is moved from one instance to another, but the holder itself is only copied (i.e. for a shared_ptr holder).
1 parent 63b7283 commit eac1ce2

File tree

2 files changed

+54
-25
lines changed

2 files changed

+54
-25
lines changed

include/pybind11/cast.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -195,8 +195,10 @@ inline PyThreadState *get_thread_state_unchecked() {
195195
#endif
196196
}
197197

198-
// Forward declaration
198+
// Forward declarations
199199
inline void keep_alive_impl(handle nurse, handle patient);
200+
inline void register_instance(void *self);
201+
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value = true);
200202

201203
class type_caster_generic {
202204
public:
@@ -302,7 +304,7 @@ class type_caster_generic {
302304
return handle((PyObject *) it_i->second).inc_ref();
303305
}
304306

305-
auto inst = reinterpret_steal<object>(PyType_GenericAlloc(tinfo->type, 0));
307+
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type, false /* don't allocate value */));
306308

307309
auto wrapper = (instance<void> *) inst.ptr();
308310

@@ -352,10 +354,9 @@ class type_caster_generic {
352354
throw cast_error("unhandled return_value_policy: should not happen!");
353355
}
354356

357+
register_instance(wrapper);
355358
tinfo->init_holder(inst.ptr(), existing_holder);
356359

357-
internals.registered_instances.emplace(wrapper->value, inst.ptr());
358-
359360
return inst.release();
360361
}
361362

include/pybind11/class_support.h

Lines changed: 49 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,49 @@ inline PyTypeObject* make_default_metaclass() {
185185
return type;
186186
}
187187

188-
/// Instance creation function for all pybind11 types. It only allocates space for the
189-
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
190-
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
188+
inline void register_instance(void *self) {
189+
auto *inst = (instance_essentials<void> *) self;
190+
get_internals().registered_instances.emplace(inst->value, self);
191+
}
192+
193+
inline bool deregister_instance(void *self) {
194+
auto *inst = (instance_essentials<void> *) self;
195+
auto type = Py_TYPE(inst);
196+
auto &registered_instances = get_internals().registered_instances;
197+
auto range = registered_instances.equal_range(inst->value);
198+
for (auto it = range.first; it != range.second; ++it) {
199+
if (type == Py_TYPE(it->second)) {
200+
registered_instances.erase(it);
201+
return true;
202+
}
203+
}
204+
return false;
205+
}
206+
207+
/// Creates a new instance which, by default, includes allocation (but not construction of) the
208+
/// wrapped C++ instance. If allocating value, the instance is registered; otherwise
209+
/// register_instance will need to be called once the value has been assigned.
210+
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= true (in cast.h)*/) {
191211
PyObject *self = type->tp_alloc(type, 0);
192212
auto instance = (instance_essentials<void> *) self;
193213
auto tinfo = get_type_info(type);
194-
instance->value = tinfo->operator_new(tinfo->type_size);
195214
instance->owned = true;
196215
instance->holder_constructed = false;
197-
get_internals().registered_instances.emplace(instance->value, self);
216+
if (allocate_value) {
217+
instance->value = tinfo->operator_new(tinfo->type_size);
218+
register_instance(self);
219+
} else {
220+
instance->value = nullptr;
221+
}
198222
return self;
199223
}
200224

225+
/// Instance creation function for all pybind11 types. It only allocates space for the
226+
/// C++ object, but doesn't call the constructor -- an `__init__` function must do that.
227+
extern "C" inline PyObject *pybind11_object_new(PyTypeObject *type, PyObject *, PyObject *) {
228+
return make_new_instance(type);
229+
}
230+
201231
/// An `__init__` function constructs the C++ object. Users should provide at least one
202232
/// of these using `py::init` or directly with `.def(__init__, ...)`. Otherwise, the
203233
/// following default function will be used which simply throws an exception.
@@ -213,25 +243,17 @@ extern "C" inline int pybind11_object_init(PyObject *self, PyObject *, PyObject
213243
return -1;
214244
}
215245

216-
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
217-
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
218-
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
246+
/// Clears all internal data from the instance and removes it from registered instances in
247+
/// preparation for deallocation.
248+
inline void clear_instance(PyObject *self) {
219249
auto instance = (instance_essentials<void> *) self;
220-
if (instance->value) {
250+
bool has_value = instance->value;
251+
if (has_value || instance->holder_constructed) {
221252
auto type = Py_TYPE(self);
222253
get_type_info(type)->dealloc(self);
223-
224-
auto &registered_instances = get_internals().registered_instances;
225-
auto range = registered_instances.equal_range(instance->value);
226-
bool found = false;
227-
for (auto it = range.first; it != range.second; ++it) {
228-
if (type == Py_TYPE(it->second)) {
229-
registered_instances.erase(it);
230-
found = true;
231-
break;
232-
}
233-
}
234-
if (!found)
254+
}
255+
if (has_value) {
256+
if (!deregister_instance(self))
235257
pybind11_fail("pybind11_object_dealloc(): Tried to deallocate unregistered instance!");
236258

237259
if (instance->weakrefs)
@@ -241,6 +263,12 @@ extern "C" inline void pybind11_object_dealloc(PyObject *self) {
241263
if (dict_ptr)
242264
Py_CLEAR(*dict_ptr);
243265
}
266+
}
267+
268+
/// Instance destructor function for all pybind11 types. It calls `type_info.dealloc`
269+
/// to destroy the C++ object itself, while the rest is Python bookkeeping.
270+
extern "C" inline void pybind11_object_dealloc(PyObject *self) {
271+
clear_instance(self);
244272
Py_TYPE(self)->tp_free(self);
245273
}
246274

0 commit comments

Comments
 (0)