QProperty: Difference between revisions
m (Add category HowTo) |
Latest revision as of 11:39, 16 July 2020
QProperty is the way to enable QML-style bindings in C++. It gives you a powerful way of expressing relations between properties in a succinct way. It is new in Qt 6.
Bindings
You can assign a binding functor directly to a QProperty. Any QProperties the functor accesses are automatically recorded. Whenever one of the recorded QProperties changes, the functor is (eventually) re-evaluated and the original property is updated. You don't have to remember to connect any signals. This is a big step forward as connecting all relevant signals to all relevant slots and manually re-evaluating everything that depends on any changes adds a lot of error-prone boiler-plate to many projects.
Lazy Evaluation
You may have noticed the "eventually" above. If you connect a signal to a slot, the evaluation mechanism is "eager": When the signal arrives, the slot is executed. You may delay the signal a bit by queuing it, or you may suppress subsequent signals of the same type by connecting them as "unique", but the fundamentals stay the same. Let's say you have a Rectangle class with width, height, and area members. The widthChanged() and heightChanged() signals are connected to a slot that calculates the area. If you change both width and height in short succession, the area will be calculated twice. The intermediate value may be way off the mark and violate some expectations elsewhere in your code.
QProperty, in contrast, evaluates lazily. That is, dependent properties are evaluated when they are read. Say you again have a Rectangle class with width, height, and area members, this time as QProperties. If you change width and height without reading area in between, nothing will be calculated. The area member is just (repeatedly) marked "dirty". The next time the area is read, it is calculated on the fly, and the value will meet your expectations. We can also save a significant amount of CPU cycles this way.
QProperty can be exposed to the meta object system, and behaves just like a getter/setter/signal property there. Code that is aware of QProperty can, however, interact with such properties in a more efficient way, using C++ bindings and lazy evaluation. This is what QtQml does.
The Price
However, this comes at a cost. In particular, we need to save a pointer to possible bindings in QProperty. Compared to the pure value you'd usually store if you follow the getter/setter/signal pattern, this adds 4 or 8 bytes to each property. For larger objects this doesn't matter much, but indeed we have many int, bool, pointer, etc. properties.
In addition, each QProperty needs to store its value so that it can properly determine whether it has changed when it's recalculated. This could probably be worked around by assuming it always changes, but then we would still have to save the bindings and dirty bits. You can, however, have getter/setter/signal properties that don't store anything at all but are always calculated on the fly.
Mind that connecting a signal to a slot does have a memory cost, too, as the connection object needs to be stored. In contrast to QProperty and bindings this is only for connections you manually create, though. QProperty additionally has a fixed overhead.
On top of this, in places where we need to send signals due to compatibility concerns, we do need to evaluate properties eagerly and find out whether they change, undoing much of the benefits QProperty offers. We cannot delay the sending of the signal until the property is read, as any reading of the property is frequently triggered by the signal itself. Therefore, we have to bite the bullet and re-evaluate any binding as soon as the property is marked dirty. This is what QNotifiedProperty does. However, if we have binding support in place for those properties, we can eventually deprecate the signals, and maybe remove them in Qt 7. In the long term we could therefore still reap the benefits of QProperty for those cases.
Obviously, replacing getters and setters with Q(Notified)Property is not binary compatible. Any publicly exposed property we want to change needs to change in Qt 6.0, or wait until Qt 7. We do have source compatibility wrappers for QProperty and QNotifiedProperty available for data members in either private or public object. We may still be lacking some details here and there, but we are certainly aiming for full source compatibility with Qt5.