> On 16 Jul 2020, at 11:19, Ulf Hermann <ulf.herm...@qt.io> wrote:
> 
>> 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


Thanks Ulf. Very informative even for someone who has spent some time on this 
already!

I don’t think Thiago’s question whether/how we can add additional 
QProperty-properties to public Qt classes without breaking binary compatibility 
is answered yet, so here’s my take on it:

The various macros involved provide this. Each property is represented by an 
instance of a struct with no data members, but just methods that forward calls 
to the accessor, which in Qt is typically the d-pointer (where the QProperty 
itself lives as well).

For pre-C++20 (where it’s possible to have zero-size structs), and for 
compilers that don’t respect the [[no_unqiue_address]] attribute, all these 
struct-instances are put into a union. In that case, a class using QProperty 
will be larger (by the same amount no matter the number of properties) than the 
same class in Qt 5. With C+++ 20 and compilers that do respect 
[[no_unique_address]], the size and layout of these classes will be the same.


Volker




_______________________________________________
Development mailing list
Development@qt-project.org
https://lists.qt-project.org/listinfo/development

Reply via email to