[Development] QProperty and library coding guide

Ulf Hermann ulf.hermann at qt.io
Thu Jul 16 11:19:22 CEST 2020


> There's a flurry of changes going in right now about using QProperty in
> QObject-derived classes. But before those begin being approved, I'd like to
> see QProperty added to our library coding guide.

Do you mean https://wiki.qt.io/Coding_Conventions ? I can certainly add 
some paragraphs there. However, let me first give an introduction here, 
and answer some of your question.


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. 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.

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 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. I expect that other modules could be adapted to do the same.


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 we 
need to change in Qt 6.0, or wait until Qt 7. We do have source 
compatibility wrappers for QProperty and QNotifiedProperty available for 
both, data members in the 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.


So, many of the questions by Thiago boil down to the following: Do we 
want to pay the price for C++ binding support in our public API?


We can break this question down by repository and module, or by 
characteristics of the properties in question, and we can decide on 
whether we want new code to use QProperty over getter/setter/signal 
properties or not. Mind, however, that bindings are not actually our own 
invention. Data bindings are a cornerstone of most modern UI frameworks, 
and Qt is actually late to the game. If we want Qt to stay relevant, 
then it needs to offer the same kind of convenience and performance that 
other frameworks offer. This would be an argument for converting all 
existing properties, and paying the price, just to make the binding API 
available.

This being said, we should realize that QtWidgets have been invented in 
the last century. They are built around signals and slots, and 
converting all those connections into bindings is likely an amount of 
work on a similar scale as rewriting all of QtWidgets from scratch. 
Therefore, the assumption so far is that we won't convert QtWidgets, and 
in particular not QWidget with its N+1 properties.

Rather, the focus is currently on classes in QtCore, QtNetwork, and 
QtGui. Those generally only have a few properties, which limits the 
memory overhead introduced by a conversion.


Finally, there is an alternative. We could provide a compatibility 
wrapper that makes getter/setter/signal properties available to 
bindings. A prototype can be seen here: 
https://codereview.qt-project.org/c/qt/qtbase/+/301937 . With such a 
thing, you would only pay a price for properties you want to use as 
QProperty. In turn, the price is higher. Considering the strategic issue 
of keeping Qt relevant, we should limit the use of such a solution to 
places where we really cannot avoid it.


best,
Ulf Hermann


More information about the Development mailing list