Object ownership露
One of the main things a binding developer should have in mind is how the C++ instances lives will cope with Python鈥檚 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鈥檒l show how Shiboken deals with object ownership and parentship, taking advantage of the information provided by the APIExtractor.
Ownership basics露
As any python binding, Shiboken-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.
In order to handle this, you should tell the generator whether the instance鈥檚 ownership belongs to the binding or to the C++ Library. When belonging to the binding, we are sure that the C++ object won鈥檛 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.
Invalidating objects露
To prevent segfaults and double frees, the wrapper objects are invalidated. An invalidated can鈥檛 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鈥檛 be sure of when the object is destroyed, unless it has a virtual destructor or the transfer is due to the special case of parent ownership.
Besides being passed as argument, the called object can have its ownership changed, like the setParent method in Qt鈥檚 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.
Objects with virtual methods露
A little bit of implementation details: 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.
One exception to this rule is when the object is created in C++, like in a factory method. This way the wrapped object is a C++ instance of the native class, not the shell one, and we cannot know when it is destroyed.
Parent-child relationship露
One special type of ownership is the parent-child relationship. Being a child of an object means that when the object鈥檚 parent dies, the C++ instance also dies, so the Python references will be invalidated. Qt鈥檚 QObject system, for example, implements this behavior, but this is valid for any C++ library with similar behavior.
Parentship heuristics露
As the parent-child relationship is very common, Shiboken 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 鈥減arent鈥 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 鈥揺nable-parent-ctor-heuristic command line switch.
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 鈥揺nable-return-value-heuristic
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鈥檚 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.
renderer.setModel(Source()) renderer.render()To solve this, you should hold a reference to the source object, like in
source = Source() renderer.setSource(source) renderer.render()
Ownership Management in the Typesystem露
For the possible values of the class attribute, see
Code Generation Terminology.
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.
<modify-argument index="1"> <define-ownership class="target" owner="target" /> </modify-argument>
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鈥檛 know when that object will be deleted, rendering the wrapper object invalid, unless you鈥檙e wrapping an object with a virtual destructor, so you can override it and be notified of its destruction.
By default it鈥檚 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.
<modify-argument index="1"> <define-ownership class="target" owner="c++" /> </modify-argument>
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鈥檚 object system, with ownership among QObject-derived classes, creating 鈥渢rees鈥 of instances.
<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 鈥榠ndex=鈥漷his鈥濃 on modify-argument) will be marked as a child of the first argument using the parent tag. To remove ownership, just use 鈥渞emove鈥 in the action attribute. Removing parentship also transfers the ownership back to python.
Invalidation after use露
Sometimes an object is created as a virtual method call argument and destroyed after the
call returned. In this case, you should use the invalidate-after-use attribute in the
modify-argument tag to mark the wrapper as invalid right after the virtual method returns.
<modify-argument index="2" invalidate-after-use="yes"/>
In this example the second argument will be invalidated after this method call.
漏 2022 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.