> 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