diff options
Diffstat (limited to 'sources/shiboken6/doc/typesystem_ownership.rst')
-rw-r--r-- | sources/shiboken6/doc/typesystem_ownership.rst | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/sources/shiboken6/doc/typesystem_ownership.rst b/sources/shiboken6/doc/typesystem_ownership.rst new file mode 100644 index 000000000..a5440e49e --- /dev/null +++ b/sources/shiboken6/doc/typesystem_ownership.rst @@ -0,0 +1,291 @@ +.. _objectownership: + +**************** +Object ownership +**************** + +One of the main things a binding developer should have in mind is +how the C++ instances lives will cope with Python's reference count. +The last thing you want is to crash a program due to a segfault +when your C++ instance was deleted and the +wrapper object tries to access the invalid memory there. + +In this section we'll show how |project| deals with object ownership +and parentship, taking advantage of the information provided by the +APIExtractor. + +Ownership basics +================ + +As any python binding, |project|-based bindings uses reference counting +to handle the life of the wrapper object (the Python object that contains the +C++ object, do not confuse with the *wrapped* C++ object). +When a reference count reaches zero, the wrapper is deleted by Python garbage +collector and tries to delete the wrapped instance, but sometimes the wrapped +C++ object is already deleted, or maybe the C++ object should not be freed after +the Python wrapper go out of scope and die, because C++ is already taking care of +the wrapped instance. + +This is not a concern for value types specified by :ref:`value-type`, which can +be freely created, copied and destroyed, however object types specified by +:ref:`object-type` pointing to C++ instances with life cycle constraints +may require attention. + +In order to handle this, you should tell the +generator whether the instance's ownership belongs to the binding or +to the C++ Library. When belonging to the binding, we are sure that the C++ object +won't be deleted by C++ code and we can call the C++ destructor when the refcount +reaches 0. Otherwise, instances owned by C++ code can be destroyed arbitrarily, +without notifying the Python wrapper of its destruction. + +By default, objects created in Python have ownership. A relevant case are +return values of virtual factory methods reimplemented in Python +(C++ Wrapper Code) which pass the bindings code. Objects obtained from C++ +(for example, ``QGuiApplication::clipoard()``) do not have ownership. + +The :ref:`shiboken-module` module provides the ``dump()`` utility function, +which prints the relevant information for an object. + +Invalidating objects +==================== + +To prevent segfaults and double frees, the wrapper objects are invalidated. +An invalidated can't be passed as argument or have an attribute or method accessed. +Trying to do this will raise RuntimeError. + +The following situations can invalidate an object: + +C++ taking ownership +-------------------- + +When an object is passed to a function or method that takes ownership of it, the wrapper +is invalidated as we can't be sure of when the object is destroyed, unless it has a +:ref:`virtual destructor <ownership-virt-method>` or the transfer is due to the special case +of :ref:`parent ownership <ownership-parent>`. + +Besides being passed as argument, the called object can have its ownership changed, like +the `setParent` method in Qt's `QObject`. + +Invalidate after use +-------------------- + +Objects marked with *invalidate-after-use* in the type system description always are +virtual method arguments provided by a C++ originated call. They should be +invalidated right after the Python function returns (see :ref:`invalidationafteruse`). + +.. _ownership-virt-method: + +Objects with virtual methods +---------------------------- + +A little bit of implementation details (see also :ref:`codegenerationterminology`): +virtual methods are supported by creating a C++ class, the **shell**, that inherits +from the class with virtual methods, the native one, and override those methods to check if +any derived class in Python also override it. + +If the class has a virtual destructor (and C++ classes with virtual methods should have), this +C++ instance invalidates the wrapper only when the overridden destructor is called. + +An instance of the **shell** is created when created in Python. However, +when the object is created in C++, like in a factory method or a parameter +to a virtual function like ``QObject::event(QEvent *)``, the wrapped object +is a C++ instance of the native class, not the **shell** one, and we cannot +know when it is destroyed. + +.. _ownership-parent: + +Parent-child relationship +========================= + +One special type of ownership is the parent-child relationship. +Being a child of an object means that when the object's parent dies, +the C++ instance also dies, so the Python references will be invalidated. +Qt's QObject system, for example, implements this behavior, but this is valid +for any C++ library with similar behavior. + +.. _ownership-parent-heuristics: + +Parentship heuristics +--------------------- + +As the parent-child relationship is very common, |project| tries to automatically +infer what methods falls into the parent-child scheme, adding the extra +directives related to ownership. + +This heuristic will be triggered when generating code for a method and: + +* The function is a constructor. +* The argument name is `parent`. +* The argument type is a pointer to an object. + +When triggered, the heuristic will set the argument named "parent" +as the parent of the object being created by the constructor. + +The main focus of this process was to remove a lot of hand written code from +type system when binding Qt libraries. For Qt, this heuristic works in all cases, +but be aware that it might not when binding your own libraries. + +To activate this heuristic, use the :ref:`--enable-parent-ctor-heuristic <parent-heuristic>` +command line switch. + +.. _return-value-heuristics: + +Return value heuristics +----------------------- + +When enabled, object returned as pointer in C++ will become child of the object on which the method +was called. + +To activate this heuristic, use the command line switch +:ref:`--enable-return-value-heuristic <return-heuristic>`. + +To disable this heuristic for specific cases, specify ``default`` as +ownership: + +.. code-block:: xml + + <modify-argument index="0"> + <define-ownership class="target" owner="default" /> + </modify-argument> + +Common pitfalls +=============== + +Not saving unowned objects references +------------------------------------- + +Sometimes when you pass an instance as argument to a method and the receiving +instance will need that object to live indefinitely, but will not take ownership +of the argument instance. In this case, you should hold a reference to the argument +instance. + +For example, let's say that you have a renderer class that will use a source class +in a setSource method but will not take ownership of it. The following code is wrong, +because when `render` is called the `Source` object created during the call to `setSource` +is already destroyed. + +.. code-block:: python + + renderer.setModel(Source()) + renderer.render() + +To solve this, you should hold a reference to the source object, like in + +.. code-block:: python + + source = Source() + renderer.setSource(source) + renderer.render() + + +Ownership Management in the Typesystem +====================================== + +Python Wrapper Code +------------------- + +For this code, the ``class`` attribute takes the value ``target`` +(see :ref:`codegenerationterminology`). + +Ownership transfer from C++ to target +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When an object currently owned by C++ has its ownership transferred +back to the target language, the binding can know for sure when the object will be deleted and +tie the C++ instance existence to the wrapper, calling the C++ destructor normally when the +wrapper is deleted. + +.. code-block:: xml + + <modify-argument index="1"> + <define-ownership class="target" owner="target" /> + </modify-argument> + +A typical use case would be returning an object allocated in C++, for +example from ``clone()`` or other factory methods. + +Ownership transfer from target to C++ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In the opposite direction, when an object ownership is transferred from the target language +to C++, the native code takes full control of the object life and you don't +know when that object will be deleted, rendering the wrapper object invalid, +unless you're wrapping an object with a virtual destructor, +so you can override it and be notified of its destruction. + +By default it's safer to just render the wrapper +object invalid and raise some error if the user tries to access +one of this objects members or pass it as argument to some function, to avoid unpleasant segfaults. +Also you should avoid calling the C++ destructor when deleting the wrapper. + +.. code-block:: xml + + <modify-argument index="1"> + <define-ownership class="target" owner="c++" /> + </modify-argument> + +Use cases would be an returning a member object by pointer +or passing an object by pointer into a function where the class +takes ownership, for example +``QNetworkAccessManager::setCookieJar(QNetworkCookieJar *)``. + +Parent-child relationship +^^^^^^^^^^^^^^^^^^^^^^^^^ + +One special type of relationship is the parent-child. When an object is called +the parent of another object (the child), the former is in charge of deleting its +child when deleted and the target language can trust that the child will be alive +as long as the parent is, unless some other method can take the C++ ownership away from the parent. + +One of the main uses of this scheme is Qt's object system, with ownership among QObject-derived +classes, creating "trees" of instances. + +.. code-block:: xml + + <modify-argument index="this"> + <parent index="1" action="add"/> + </modify-argument> + +In this example, the instance with the method that is being invoked (indicated by 'index="this"' on +modify-argument) will be marked as a child +of the first argument using the `parent` tag. To remove ownership, just use "remove" in the action attribute. **Removing +parentship also transfers the ownership back to python.** + +See `Object Trees and Object Ownership in Qt`_. + +.. _`Object Trees and Object Ownership in Qt`: https://doc.qt.io/qt-6/objecttrees.html + +C++ Wrapper Code +---------------- + +For this code, the ``class`` attribute takes the value ``native``. The +modifications affect code called from within C++, typically when calling +virtual C++ methods reimplemented in Python +(see :ref:`codegenerationterminology`). + +Return values of virtual functions +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The ownership of C++ objects returned by pointer should be set to ``c++`` to +prevent them from being deleted by Python, since objects created +in Python have ownership by default. + +Ownership transfers specified for other arguments do not have any effect. + +.. _invalidationafteruse: + +Invalidation after use +^^^^^^^^^^^^^^^^^^^^^^ + +Sometimes an object is created in C++ and passed as a virtual method call +argument and destroyed after the call returned +(see :ref:`ownership-virt-method`). +In this case, you should use the ``invalidate-after-use`` attribute in the +:ref:`modify-argument` tag to mark the wrapper as invalid right after the +virtual method returns. + +.. code-block:: xml + + <modify-argument index="2" invalidate-after-use="yes"/> + +In this example the second argument will be invalidated after this method call. |