Skip to content

Commit 1c462d6

Browse files
committed
Low-level interface: replaced nb::inst_wrap, added nb::inst_replace_*
The ``nb::inst_wrap`` API had a serious flaw: there was no way to control whether the instance, when eventually garbage collected, should free its storage via a call to the untyped ``operator delete`` (following the in-place destructor). This commit removes the function completely. While technically an API break, there is no alternative because the function was broken in practice. The commit adds two more fine-grained replacements: - ``nb::inst_take_ownership()``: analogous to a cast with ``nb::rv_policy::take_ownership``: deleting the instance will call the C++ in-place destructor and untyped ``operator delete``. - ``nb::inst_reference()``: analogous to a cast with ``nb::rv_policy::reference`` or ``nb::rv_policy::reference_internal`` depending on usage: deleting the instance will neither call the in-place destructor nor the untyped ``operator delete``. If an optional ``parent`` parameter is provided, nanobind will ensure that the parent remains alive while the instance exists. Besides these changes, the commit also adds two compound operations: ``nb::inst_replace_copy()`` and ``nb::inst_replace_move()`` that replace the contents of an already initialized instance by copying or moving from another instance.
1 parent 1f5c94f commit 1c462d6

File tree

7 files changed

+258
-96
lines changed

7 files changed

+258
-96
lines changed

docs/api_core.rst

+100-27
Original file line numberDiff line numberDiff line change
@@ -2113,8 +2113,7 @@ Low-level type and instance access
21132113
nanobind exposes a low-level interface to provide fine-grained control over
21142114
the sequence of steps that instantiates a Python object wrapping a C++
21152115
instance. An thorough explanation of these features is provided in a
2116-
:ref:`separate section <lowlevel>`. The function listing below merely
2117-
summarizes their signatures.
2116+
:ref:`separate section <lowlevel>`.
21182117

21192118
Type objects
21202119
^^^^^^^^^^^^
@@ -2144,9 +2143,9 @@ Type objects
21442143
Return a reference to supplemental data stashed in a type object.
21452144
The type ``T`` must exactly match the type specified in the
21462145
:cpp:class:`nb::supplement\<T\> <supplement>` annotation used when
2147-
creating the type; no type check is performed, and accessing an
2148-
incorrect supplement type may crash the interpreter.
2149-
See :cpp:class:`supplement`.
2146+
creating the type; no type check is performed, and invalid supplement
2147+
accesses may crash the interpreter. Also refer to
2148+
:cpp:class:`nb::supplement\<T\> <supplement>`.
21502149

21512150
.. cpp:function:: str type_name(handle h)
21522151

@@ -2163,6 +2162,14 @@ Type objects
21632162
Instances
21642163
^^^^^^^^^
21652164

2165+
The documentation below refers to two per-instance flags with the following meaning:
2166+
2167+
- *ready*: is the instance fully constructed? nanobind will not permit passing
2168+
the instance to a bound C++ function when this flag is unset.
2169+
2170+
- *destruct*: should nanobind call the C++ destructor when the instance is
2171+
garbage-collected?
2172+
21662173
.. cpp:function:: bool inst_check(handle h)
21672174

21682175
Returns ``true`` if `h` represents an instance of a type that was
@@ -2171,63 +2178,129 @@ Instances
21712178
.. cpp:function:: template <typename T> T * inst_ptr(handle h)
21722179

21732180
Assuming that `h` represents an instance of a type that was previously bound
2174-
via :cpp:class:`class_`, return a pointer to the C++ instance.
2181+
via :cpp:class:`class_`, return a pointer to the underlying C++ instance.
21752182

2176-
The function *does not check* that `T` is consistent with the type of `h`.
2183+
The function *does not check* that `h` actually contains an instance with
2184+
C++ type `T`.
21772185

21782186
.. cpp:function:: object inst_alloc(handle h)
21792187

2180-
Assuming that `h` represents a bound type (see :cpp:func:`type_check`),
2181-
allocate an uninitialized Python object of type `h` and return it.
2188+
Assuming that `h` represents a type object that was previously created via
2189+
:cpp:class:`class_` (see :cpp:func:`type_check`), allocate an unitialized
2190+
object of type `h` and return it. The *ready* and *destruct* flags of the
2191+
returned instance are both set to ``false``.
21822192

21832193
.. cpp:function:: object inst_alloc_zero(handle h)
21842194

2185-
Assuming that `h` represents a bound type (see :cpp:func:`type_check`),
2186-
allocate a zero-initialized Python object of type `h` and return it. This
2187-
operation is equilvalent to calling :cpp:func:`inst_alloc` followed by
2195+
Assuming that `h` represents a type object that was previously created via
2196+
:cpp:class:`class_` (see :cpp:func:`type_check`), allocate a zero-initialized
2197+
object of type `h` and return it. The *ready* and *destruct* flags of the
2198+
returned instance are both set to ``true``.
2199+
2200+
This operation is equivalent to calling :cpp:func:`inst_alloc` followed by
21882201
:cpp:func:`inst_zero`.
21892202

2190-
.. cpp:function:: object inst_wrap(handle h, void * p)
2203+
.. cpp:function:: object inst_reference(handle h, void * p, handle parent = handle())
21912204

2192-
Assuming that `h` represents an instance of a type that was previously bound
2193-
via :cpp:class:`class_`, create an object of type `h` that wraps an existing
2194-
C++ instace `p`.
2205+
Assuming that `h` represents a type object that was previously created via
2206+
:cpp:class:`class_` (see :cpp:func:`type_check`) create an object of type
2207+
`h` that wraps an existing C++ instance `p`.
2208+
2209+
The *ready* and *destruct* flags of the returned instance are respectively
2210+
set to ``true`` and ``false``.
2211+
2212+
This is analogous to casting a C++ object with return value policy
2213+
:cpp:enumerator:`rv_policy::reference`.
2214+
2215+
If a `parent` object is specified, the instance keeps this parent alive
2216+
while the newly created object exists. This is analogous to casting a C++
2217+
object with return value policy
2218+
:cpp:enumerator:`rv_policy::reference_internal`.
2219+
2220+
.. cpp:function:: object inst_take_ownership(handle h, void * p)
2221+
2222+
Assuming that `h` represents a type object that was previously created via
2223+
:cpp:class:`class_` (see :cpp:func:`type_check`) create an object of type
2224+
`h` that wraps an existing C++ instance `p`.
2225+
2226+
The *ready* and *destruct* flags of the returned instance are both set to
2227+
``true``.
2228+
2229+
This is analogous to casting a C++ object with return value policy
2230+
:cpp:enumerator:`rv_policy::take_ownership`.
21952231

21962232
.. cpp:function:: void inst_zero(handle h)
21972233

2198-
Zero-initialize the contents of `h`.
2234+
Zero-initialize the contents of `h`. Sets the *ready* and *destruct* flags
2235+
to ``true``.
21992236

22002237
.. cpp:function:: bool inst_ready(handle h)
22012238

2202-
Query the *ready* field of the object `h`.
2239+
Query the *ready* flag of the instance `h`.
22032240

2204-
.. cpp:function:: void inst_mark_ready(handle h)
2241+
.. cpp:function:: std::pair<bool, bool> inst_state(handle h)
22052242

2206-
Mark the object `h` as *ready*.
2243+
Separately query the *ready* and *destruct* flags of the instance `h`.
22072244

2208-
.. cpp:function:: std::pair<bool, bool> inst_state(handle h)
2245+
.. cpp:function:: void inst_mark_ready(handle h)
22092246

2210-
Query the *ready* and *destruct* fields of the object `h`.
2247+
Simultaneously set the *ready* and *destruct* flags of the instance `h` to ``true``.
22112248

22122249
.. cpp:function:: void inst_set_state(handle h, bool ready, bool destruct)
22132250

2214-
Set the *ready* and *destruct* fields of the object `h`.
2251+
Separately set the *ready* and *destruct* flags of the instance `h`.
22152252

22162253
.. cpp:function:: void inst_destruct(handle h)
22172254

2218-
Destruct the object `h`.
2255+
Destruct the instance `h`. This entails calling the C++ destructor if the
2256+
*destruct* flag is set and then setting the *ready* and *destruct* fields to
2257+
``false``.
22192258

22202259
.. cpp:function:: void inst_copy(handle dst, handle src)
22212260

2222-
Move-construct the contents of `src` into `dst` and mark `dst` as *ready*.
2261+
Copy-construct the contents of `src` into `dst` and set the *ready* and
2262+
*destruct* flags of `dst` to ``true``.
2263+
2264+
`dst` should be an uninitialized instance of the same type. Note that
2265+
setting the *destruct* flag may be problematic if `dst` is an offset into an
2266+
existing object created using :cpp:func:`inst_reference` (the destructor
2267+
will be called multiple times in this case). If so, you must use
2268+
:cpp:func:`inst_set_state` to disable the flag following the call to
2269+
:cpp:func:`inst_copy`.
22232270

22242271
.. cpp:function:: void inst_move(handle dst, handle src)
22252272

2226-
Copy-construct the contents of `src` into `dst` and mark `dst` as *ready*.
2273+
Move-construct the contents of `src` into `dst` and set the *ready* and
2274+
*destruct* flags of `dst` to ``true``.
2275+
2276+
`dst` should be an uninitialized instance of the same type. Note that
2277+
setting the *destruct* flag may be problematic if `dst` is an offset into an
2278+
existing object created using :cpp:func:`inst_reference` (the destructor
2279+
will be called multiple times in this case). If so, you must use
2280+
:cpp:func:`inst_set_state` to disable the flag following the call to
2281+
:cpp:func:`inst_move`.
2282+
2283+
.. cpp:function:: void inst_replace_copy(handle dst, handle src)
2284+
2285+
Destruct the contents of `dst` (even if the *destruct* flag is ``false``).
2286+
Next, copy-construct the contents of `src` into `dst` and set the *ready*
2287+
flag of ``dst``. The value of the *destruct* flag is subsequently set to its
2288+
value prior to the call.
2289+
2290+
This operation is useful to replace the contents of one instance with that
2291+
of another regardless of whether `dst` has been created using
2292+
:cpp:func:`inst_alloc`, :cpp:func:`inst_reference`, or
2293+
:cpp:func:`inst_take_ownership`.
2294+
2295+
.. cpp:function:: void inst_replace_move(handle dst, handle src)
2296+
2297+
Analogous to :cpp:func:`inst_replace_copy`, except that a move constructor
2298+
is used here.
22272299

22282300
.. cpp:function:: str inst_name(handle h)
22292301

2230-
Return the full (module-qualified) name of the instance's type object as a Python string.
2302+
Return the full (module-qualified) name of the instance's type object as a
2303+
Python string.
22312304

22322305
Global flags
22332306
------------

docs/lowlevel.rst

+65-40
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ instance. This is useful when writing generic binding code that manipulates
1111
nanobind-based objects of various types.
1212

1313
Given a previous :cpp:class:`nb::class_\<...\> <class_>` binding declaration,
14-
this interface can be used to look up the Python type object associated with
15-
a C++ class named ``MyClass``.
14+
the :cpp:func:`nb::type\<T\>() <type>` template function can be used to look up
15+
the Python type object associated with a C++ class named ``MyClass``.
1616

1717
.. code-block:: cpp
1818
@@ -28,9 +28,10 @@ redundant in this case):
2828
2929
assert(py_type.is_valid() && nb::type_check(py_type));
3030
31-
nanobind knows the size, alignment, and C++ RTTI ``std::type_info``
32-
record of all bound types. They can be queried on the fly in situations
33-
where this is useful.
31+
nanobind knows the size, alignment, and C++ RTTI ``std::type_info`` record of
32+
all bound types. They can be queried on the fly via :cpp:func:`nb::type_size()
33+
<type_size>`, :cpp:func:`nb::type_align() <type_size>`, and
34+
:cpp:func:`nb::type_info() <type_size>` in situations where this is useful.
3435

3536
.. code-block:: cpp
3637
@@ -47,9 +48,11 @@ to prevent undefined behavior. It must first be initialized.
4748
4849
nb::object py_inst = nb::inst_alloc(py_type);
4950
50-
We can confirm that this newly created instance is managed by nanobind,
51-
that it has the correct type in Python, and that it is not ``ready``
52-
(i.e. uninitialized):
51+
We can confirm via :cpp:func:`nb::inst_check() <inst_check>` that this newly
52+
created instance is managed by nanobind, that it has the correct type in
53+
Python. Calling :cpp:func:`nb::inst_ready() <inst_ready>` reveals that the
54+
*ready* flag of the instance is set to ``false`` (i.e., it is still
55+
uninitialized).
5356

5457
.. code-block:: cpp
5558
@@ -58,16 +61,17 @@ that it has the correct type in Python, and that it is not ``ready``
5861
!nb::inst_ready(py_inst));
5962
6063
For simple *plain old data* (POD) types, the :cpp:func:`nb::inst_zero()
61-
<inst_zero>` function can be used to zero-initialize the object and mark it
64+
<inst_zero>` function can be used to *zero-initialize* the object and mark it
6265
as ready.
6366

6467
.. code-block:: cpp
6568
6669
nb::inst_zero(py_inst);
6770
assert(nb::inst_ready(py_inst));
6871
69-
We can destruct this default instance and convert it back to non-ready
70-
status. This memory region can then be reinitialized once more.
72+
We can destruct this default instance via :cpp:func:`nb::inst_destruct()
73+
<inst_destruct>` and convert it back to non-ready status. This memory region
74+
can then be reinitialized once more.
7175

7276
.. code-block:: cpp
7377
@@ -88,9 +92,9 @@ by nanobind.
8892
new (ptr) MyClass(/* constructor arguments go here */);
8993
9094
Following this constructor call, we must inform nanobind that the instance
91-
object is now fully constructed. When its reference count reaches zero,
92-
nanobind will automatically call the in-place destructor
93-
(``MyClass::~MyClass``).
95+
object is now fully constructed via :cpp:func:`nb::inst_mark_ready()
96+
<inst_mark_ready>`. When its reference count reaches zero, nanobind will then
97+
automatically call the in-place destructor (``MyClass::~MyClass``).
9498

9599
.. code-block:: cpp
96100
@@ -105,12 +109,14 @@ the C++ destructor and mark the Python object as non-ready).
105109
nb::inst_destruct(py_inst);
106110
107111
Another useful feature is that nanobind can copy- or move-construct ``py_inst``
108-
from another instance of the same type. This calls the C++ copy or move
109-
constructor and transitions ``py_inst`` back to ``ready`` status. Note that
110-
this is equivalent to calling an in-place version of these constructors above
111-
but compiles to more compact code (the :cpp:class:`nb::class_\<MyClass\>
112-
<class_>` declaration had already created bindings for both constructors, and
113-
this simply calls those bindings).
112+
from another instance of the same type via :cpp:func:`nb::inst_copy()
113+
<inst_copy>` and :cpp:func:`nb::inst_move() <inst_move>`. These functions call
114+
the C++ copy or move constructor and transition ``py_inst`` back to ``ready``
115+
status. This is equivalent to calling an in-place version of these constructors
116+
followed by a call to :cpp:func:`nb::inst_mark_ready() <inst_mark_ready>` but
117+
compiles to more compact code (the :cpp:class:`nb::class_\<MyClass\> <class_>`
118+
declaration had already created bindings for both constructors, and this simply
119+
calls those bindings).
114120

115121
.. code-block:: cpp
116122
@@ -119,6 +125,19 @@ this simply calls those bindings).
119125
else
120126
nb::inst_move(/* dst = */ py_inst, /* src = */ some_other_instance);
121127
128+
Both functions assume that the destination object is uninitialized. Two
129+
alternative versions :cpp:func:`nb::inst_replace_copy() <inst_replace_copy>`
130+
and :cpp:func:`nb::inst_replace_move() <inst_replace_move>` destruct an
131+
initialized instance and replace it with the contents of another by either
132+
copying or moving.
133+
134+
.. code-block:: cpp
135+
136+
if (copy_instance)
137+
nb::inst_replace_copy(/* dst = */ py_inst, /* src = */ some_other_instance);
138+
else
139+
nb::inst_replace_move(/* dst = */ py_inst, /* src = */ some_other_instance);
140+
122141
Note that these functions are all *unsafe* in the sense that they do not
123142
verify that their input arguments are valid. This is done for
124143
performance reasons, and such checks (if needed) are therefore the
@@ -127,10 +146,14 @@ only be called with nanobind type objects, and functions labeled
127146
``nb::inst_*`` should only be called with nanobind instance objects.
128147

129148
The functions :cpp:func:`nb::type_check() <type_check>` and
130-
:cpp:func:`nb::inst_check() <inst_check>` are exceptions to this rule: they
131-
accept any Python object and test whether something is a nanobind type or
149+
:cpp:func:`nb::inst_check() <inst_check>` are exceptions to this rule:
150+
they accept any Python object and test whether something is a nanobind type or
132151
instance object.
133152

153+
Two further functions :cpp:func:`nb::type_name() <type_name>` and
154+
:cpp:func:`nb::inst_name() <inst_name>` determine the type name associated with
155+
a type or instance thereof. These also accept non-nanobind types and instances.
156+
134157
Even lower-level interface
135158
--------------------------
136159

@@ -149,18 +172,14 @@ The functions :cpp:func:`nb::inst_zero() <inst_zero>`,
149172
flags to ``true``, and :cpp:func:`nb::inst_destruct() <inst_destruct>` sets
150173
both of them to ``false``.
151174

152-
In rare situations, the destructor should *not* be invoked when the
153-
instance is garbage collected, for example when working with a nanobind
154-
instance representing a field of a parent instance created using the
175+
In rare situations, the destructor should *not* be invoked when the instance is
176+
garbage collected, for example when working with a nanobind instance
177+
representing a field of a parent instance created using the
155178
:cpp:enumerator:`nb::rv_policy::reference_internal
156179
<rv_policy::reference_internal>` return value policy. The library therefore
157-
exposes two more functions that can be used to read/write these two flags
158-
individually.
159-
160-
.. code-block:: cpp
161-
162-
void inst_set_state(handle h, bool ready, bool destruct);
163-
std::pair<bool, bool> inst_state(handle h);
180+
exposes two more functions :cpp:func:`nb::inst_state() <inst_state>` and
181+
:cpp:func:`nb::inst_set_state() <inst_set_state>` that can be used to access
182+
them individually.
164183

165184
Referencing existing instances
166185
------------------------------
@@ -178,19 +197,25 @@ with the binding ``py_type``.
178197
... omitted, see the previous examples ...
179198
180199
What if the C++ instance already exists? nanobind also supports this case via
181-
the :cpp:func:`nb::inst_wrap() <inst_wrap>` function—in this case, the Python
182-
object references the existing memory region, which is potentially (slightly)
183-
less efficient due to the need for an extra indirection.
200+
the :cpp:func:`nb::inst_reference() <inst_reference>` and
201+
:cpp:func:`nb::inst_take_ownership() <inst_take_ownership>` functions—in this
202+
case, the Python object references the existing memory region, which is
203+
potentially (slightly) less efficient due to the need for an extra indirection.
184204

185205
.. code-block:: cpp
186206
187207
MyClass *inst = new MyClass();
188-
nb::object py_inst = nb::inst_wrap(py_type, inst);
189208
190-
// Mark as ready, garbage-collecting 'py_inst' will cause 'inst' to be
191-
// deleted as well. Call nb::inst_set_state (documented above) for more
192-
// fine-grained control.
193-
nb::inst_mark_ready(py_inst);
209+
// Transfer ownership of 'inst' to Python (which will use a delete
210+
// expression to free it when the Python instance is garbage collected)
211+
nb::object py_inst = nb::inst_take_ownership(py_type, inst);
212+
213+
// We can also wrap C++ instances that should not be destructed since
214+
// they represent offsets into another data structure. In this case,
215+
// the optional 'parent' parameter ensures that 'py_inst' remains alive
216+
// while 'py_subinst' exists to prevent undefined behavior.
217+
nb::object py_subinst = nb::inst_reference(
218+
py_field_type, &inst->field, /* parent = */ py_inst);
194219
195220
.. _supplement:
196221

0 commit comments

Comments
 (0)