[Development] New property system
lars.knoll at qt.io
Wed Aug 26 09:18:49 CEST 2020
After the long discussions a few weeks back about the new property system and some trial to port the existing properties over to the new system, it became somewhat clear to me that the approach we had tried so far won’t work and be ready in time for the feature freeze.
The main problems identified where
* the reliance on [[no_unique_address]] which is a C++20 feature
* The additional storage overhead for bindings
* the fact that the decision whether a property can use bindings or not is a decision that changes the ABI. This implied, that we could not add binding support to a property without breaking binary compatibility after 6.0 has been released.
So I sat down and did quite a bit of work redesigning the system over the last three weeks. I am pretty happy with the result I now came up with, and hope to have it all merged before the feature freeze.
The redesigned system keeps most of the low level API the same, so you still have QProperty<T> and QPropertyBinding to create bindings. But it does integrate much better with the existing property system that we have from Qt 5.x.
QProperty<T> has as a design choice coupled the storage of the property data with an additional pointer to store any possible binding related data. The redesign breaks this hard dependency, and allows separating the data storage from the binding data.
I’ve added a new QBindingStorage class that can store bindings for a whole object. QObject now has one of those by default. If bindings aren’t used this comes down to two additional pointers inside QObjectPrivate (and not one additional pointer per property). The class is currently marked internal, but can in principle be used to add a binding storage also to other types of objects.
Together with QBindingStorage, we now have a couple of new classes to store properties without memory overhead. As QBindingStorage and those classes are aimed at QObject derived classes (others should use QProperty<T> with inline binding storage), those classes are called QObjectBindable/Computed/CompatProperty. They all add binding support to a property, but serve slightly different purposes:
* QObjectBindableProperty should be the default to use, especially for new code. It does lazy binding evaluation, and does support an optional changed signal.
* QObjectComputedProperty can be used for read-only properties where the value is computed on the fly. It supports being used in binding expressions, but you can not create a binding on it (as it’s read-only).
* QObjectCompatProperty is there to help port existing properties over and make them bindable. It does eager binding evaluation and calls the existing property setter. This is the class to use for easy porting if you can’t or don’t want to refactor a lot of code. It needs to evaluate bindings eagerly, as many of the existing property setters have side effects.
Using those classes happens through a Q_OBJECT_BINDABLE/COMPUTED/COMPAT_PROPERTY macro. We need the macro to be able to get back from the properties to the binding storage. This is the one place where we rely on a non standard (but supported by all our compilers) feature. Namely the ability to call offsetof() on non-standard types.
Binding support through the public API is now exposed through a QBindable<T> class, that provides the binding interface towards those properties. In addition to the getter and setter, the object will also need one Bindable<Type> bindableProp() for a property prop to make the binding support accessible. This method will need to get implemented by the developer.
As a final piece, moc can also generate some support for introspecting those bindings. This simply requires adding a “BINDABLE bindableProp” section to the existing Q_PROPERTY() macro. With that, you can set and inspect bindings though QMetaProperty.
You can find an example where I ported QAbstractAnimation to enable bindings here: https://codereview.qt-project.org/c/qt/qtbase/+/310748.
As you can see, it only required changing the storage of the property data to use Q_OBJECT_BINDABLE(COMPUTED)_PROPERTY, adding the bindingProp() accessors to the public API and making them known to the meta object system by adding the BINDABLE section to Q_PROPERTY().
To summarise the advantages of the redesigned system:
* Very little memory overhead if bindings aren’t used
* Very low runtime overhead if bindings aren’t used
* Binding support can be added to properties in QObjects in a binary compatible way
* Straight forward extension of the existing and known property system
* Much easier to port existing properties
https://codereview.qt-project.org/c/qt/qtbase/+/310748 is also the end of the patch series that does implement the required infrastructure. It’s still lacking a bit of documentation, but the series is now pretty clean and should be in a good state for reviewing.
More information about the Development