QtCS25 - QProperty and QBindable: Difference between revisions
(added summary) |
Ulf Hermann (talk | contribs) |
||
| (One intermediate revision by the same user not shown) | |||
| Line 1: | Line 1: | ||
==Session Summary== | ==Session Summary== | ||
''Without integration with moc, Q_PROPERTY and QML, QProperty and QBindable would be fairly simple and self-contained pieces of code. They could be moved to a separate library, e.g. QtBindable and live there without hurting anyone.'' | |||
'' | ''Conversely, the deep integration with moc, Q_PROPERTY and QML gives us a lot of headaches.'' | ||
* ''QBindingStorage increases the size of all QObjects by two pointers.'' | |||
* ''Granted: Simple properties are easy to use'' | |||
** ''Just slap a BINDABLE on it and make the getter and setter "default".'' | |||
* ''Spurious dependencies when when writing custom setters:'' | |||
** ''whenever you need to check some (other) property's value, you need valueBypassingBindings()'' | |||
** ''ditto when producing argument for change signal'' | |||
* ''If setters have side effects, you need to do, in the right places:'' | |||
** ''removeBindingUnlessInWrapper()'' | |||
** ''setValueBypassingBindings()'' | |||
** ''notify()'' | |||
** ''Easy to get wrong and results in subtle, hard to detect problems.'' | |||
* ''QML engine has to handle all permutations of QProperty and classic properties'' | |||
** ''bloats code'' | |||
** ''increases complexity in many places'' | |||
** ''QProperty not noticably faster when used from QML'' | |||
=== ''Why QProperty anyway?'' === | |||
==== ''Offer a convenient C++ API for bindings.'' ==== | |||
* ''QProperty and QPropertyBinding work great on their own'' | |||
** ''very ergonomic'' | |||
** ''quite performant'' | |||
* ''moc/Q_PROPERTY integration is extra you don't really need'' | |||
** ''QProperty and QPropertyBinding work fine without any QObject'' | |||
** ''external adapter makes getter/setter/signal Q_PROPERTY integrate with QProperty'' | |||
*** ''Special constructors for QBindable that take an object/property pair'' | |||
==== ''Enable Qt Quick Compiler to generate better C++ code for bindings'' ==== | |||
''Has steep preconditions:'' | |||
* ''binding needs to be compilable'' | |||
* ''need to use direct mode'' | |||
** ''implies private API'' | |||
** ''implies accessible headers'' | |||
* ''property written has to be bindable'' | |||
* ''properties read from have to be bindable'' | |||
''In practice this rarely happens.'' | |||
=== ''Suggestions'' === | |||
==== ''Ulf'' ==== | |||
# ''Warn about properties with BINDABLE but without NOTIFY in QML'' | |||
#* ''Those become a problem at step 4'' | |||
#* ''You can easily add change signals to bindable properties, though'' | |||
# ''Deprecate the BINDABLE attribute to Q_PROPERTY. We probably can't have moc produce a warning about it right away, but we can already adapt the documentation.'' | |||
# ''Deprecate QObjectBindableProperty and friends.'' | |||
# ''Revert our own bindable Q_PROPERTYs to be backed by simple data members rather than QProperty again. Implement the public fooBindable() methods in terms of the Q_PROPERTY adaptors for QBindable and deprecate them. Drop all the BINDABLEs in private API.'' | |||
# ''Remove the integration with QProperty and QBindable in QML. QML will always use the READ/WRITE/NOTIFY methods again and ignore any BINDABLE.'' | |||
# ''In Qt7, move the remaining QProperty and QBindable code into a separate library.'' | |||
''Undecided: Using BINDABLE in simple Q_PROPERTY is much less boiler plate than READ/WRITE/NOTIFY. We need a replacement for that.'' | |||
==== ''Volker'' ==== | |||
''Yes, remove Q_PROPERTY integration of QProperty via some migration process.'' | |||
''Simplified Q_PROPERTY declaration that removes the getter/setter boilerplate from Q_PROPERTY'' | |||
''class Object : public QObject | |||
{ | |||
Q_OBJECT | |||
Q_PROPERTY(value) | |||
public: | |||
<nowiki> // ~~~</nowiki> | |||
int value() const; | |||
void setValue() const; | |||
signals: | |||
void valueChanged(); | |||
};'' | |||
* ''Pro: Simpler Q_PROPERTY declaration'' | |||
* ''Con:'' | |||
** ''You still need to write the setters, getters, and signals. That's more boilerplate.'' | |||
** ''Tying the naming together is magic'' | |||
==== ''Thiago'' ==== | |||
''class Object : public QObject | |||
{ | |||
Q_OBJECT | |||
public: | |||
Q_PROPERTY_FULL(int, READ, int value() noexcept, | |||
WRITE, void setValue(int), | |||
NOTIFY, void valueChanged()) | |||
};'' | |||
''It shouldn't be too difficult to make the macro output all positive even- numbered fields into the header. Or if we can find a way to insert a prefix that then turns the word into a macro that expands to empty:'' | |||
''Q_PROPERTY_FULL(int, READ int value() noexcept, | |||
WRITE void setValue(int), | |||
NOTIFY void valueChanged())'' | |||
* ''Pro: Removes all the boilerplate from the header'' | |||
* ''Con:'' | |||
** ''Extremely elaborate macro'' | |||
** ''What about the implementations of getters and setters?'' | |||
==== ''Ulf'' ==== | |||
''Why all the ceremony? What people probably want is:'' | |||
''Q_DEFAULT_PROPERTY(int, value)'' | |||
''That would expand to:'' | |||
''Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged FINAL) | |||
public: | |||
int value() const { return m_value; } | |||
void setValue(int value) | |||
{ | |||
if (value == m_value) | |||
return; | |||
m_value = value; | |||
Q_EMIT valueChanged(); | |||
} | |||
Q_SIGNALS: | |||
void valueChanged(); | |||
private: | |||
int m_value = {};'' | |||
''Now you just need to make sure you keep your Q_DEFAULT_PROPERTY in the private section of your class and you're fine. The capital "V" in "setValue" is probably challenging to express in a macro but otherwise this should be trivial.'' | |||
''If we can't express the 'V', let's make it Q_DEFAULT_PROPERTY(int, value, setValue). That's still quite good compared to what we have!'' | |||
''Pro: Removes actually all the boilerplate Con: * Even more elaborate macro (but not much logic involved) * Does not cater to custom naming schemes, readonly, constant, etc'' | |||
''NB: We can have Q_CONSTANT_PROPERTY, Q_READONLY_PROPERTY and maybe Q_CUSTOM_PROPERTY in addition. Q_CUSTOM_PROPERTY would specify all the getter, setter, signal names. We can even have Q_CUSTOM_CONSTANT_PROPERTY and Q_CUSTOM_READONLY_PROPERTY, but I'll stop here.'' | |||
==Session Owners== | ==Session Owners== | ||
Latest revision as of 14:30, 8 May 2025
Session Summary
Without integration with moc, Q_PROPERTY and QML, QProperty and QBindable would be fairly simple and self-contained pieces of code. They could be moved to a separate library, e.g. QtBindable and live there without hurting anyone.
Conversely, the deep integration with moc, Q_PROPERTY and QML gives us a lot of headaches.
- QBindingStorage increases the size of all QObjects by two pointers.
- Granted: Simple properties are easy to use
- Just slap a BINDABLE on it and make the getter and setter "default".
- Spurious dependencies when when writing custom setters:
- whenever you need to check some (other) property's value, you need valueBypassingBindings()
- ditto when producing argument for change signal
- If setters have side effects, you need to do, in the right places:
- removeBindingUnlessInWrapper()
- setValueBypassingBindings()
- notify()
- Easy to get wrong and results in subtle, hard to detect problems.
- QML engine has to handle all permutations of QProperty and classic properties
- bloats code
- increases complexity in many places
- QProperty not noticably faster when used from QML
Why QProperty anyway?
Offer a convenient C++ API for bindings.
- QProperty and QPropertyBinding work great on their own
- very ergonomic
- quite performant
- moc/Q_PROPERTY integration is extra you don't really need
- QProperty and QPropertyBinding work fine without any QObject
- external adapter makes getter/setter/signal Q_PROPERTY integrate with QProperty
- Special constructors for QBindable that take an object/property pair
Enable Qt Quick Compiler to generate better C++ code for bindings
Has steep preconditions:
- binding needs to be compilable
- need to use direct mode
- implies private API
- implies accessible headers
- property written has to be bindable
- properties read from have to be bindable
In practice this rarely happens.
Suggestions
Ulf
- Warn about properties with BINDABLE but without NOTIFY in QML
- Those become a problem at step 4
- You can easily add change signals to bindable properties, though
- Deprecate the BINDABLE attribute to Q_PROPERTY. We probably can't have moc produce a warning about it right away, but we can already adapt the documentation.
- Deprecate QObjectBindableProperty and friends.
- Revert our own bindable Q_PROPERTYs to be backed by simple data members rather than QProperty again. Implement the public fooBindable() methods in terms of the Q_PROPERTY adaptors for QBindable and deprecate them. Drop all the BINDABLEs in private API.
- Remove the integration with QProperty and QBindable in QML. QML will always use the READ/WRITE/NOTIFY methods again and ignore any BINDABLE.
- In Qt7, move the remaining QProperty and QBindable code into a separate library.
Undecided: Using BINDABLE in simple Q_PROPERTY is much less boiler plate than READ/WRITE/NOTIFY. We need a replacement for that.
Volker
Yes, remove Q_PROPERTY integration of QProperty via some migration process.
Simplified Q_PROPERTY declaration that removes the getter/setter boilerplate from Q_PROPERTY
class Object : public QObject
{
Q_OBJECT
Q_PROPERTY(value)
public:
// ~~~
int value() const;
void setValue() const;
signals:
void valueChanged();
};
- Pro: Simpler Q_PROPERTY declaration
- Con:
- You still need to write the setters, getters, and signals. That's more boilerplate.
- Tying the naming together is magic
Thiago
class Object : public QObject
{
Q_OBJECT
public:
Q_PROPERTY_FULL(int, READ, int value() noexcept,
WRITE, void setValue(int),
NOTIFY, void valueChanged())
};
It shouldn't be too difficult to make the macro output all positive even- numbered fields into the header. Or if we can find a way to insert a prefix that then turns the word into a macro that expands to empty:
Q_PROPERTY_FULL(int, READ int value() noexcept,
WRITE void setValue(int),
NOTIFY void valueChanged())
- Pro: Removes all the boilerplate from the header
- Con:
- Extremely elaborate macro
- What about the implementations of getters and setters?
Ulf
Why all the ceremony? What people probably want is:
Q_DEFAULT_PROPERTY(int, value)
That would expand to:
Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged FINAL)
public:
int value() const { return m_value; }
void setValue(int value)
{
if (value == m_value)
return;
m_value = value;
Q_EMIT valueChanged();
}
Q_SIGNALS:
void valueChanged();
private:
int m_value = {};
Now you just need to make sure you keep your Q_DEFAULT_PROPERTY in the private section of your class and you're fine. The capital "V" in "setValue" is probably challenging to express in a macro but otherwise this should be trivial.
If we can't express the 'V', let's make it Q_DEFAULT_PROPERTY(int, value, setValue). That's still quite good compared to what we have!
Pro: Removes actually all the boilerplate Con: * Even more elaborate macro (but not much logic involved) * Does not cater to custom naming schemes, readonly, constant, etc
NB: We can have Q_CONSTANT_PROPERTY, Q_READONLY_PROPERTY and maybe Q_CUSTOM_PROPERTY in addition. Q_CUSTOM_PROPERTY would specify all the getter, setter, signal names. We can even have Q_CUSTOM_CONSTANT_PROPERTY and Q_CUSTOM_READONLY_PROPERTY, but I'll stop here.
Session Owners
Ulf Hermann
Notes
- introduced with big fanfare in Qt 6
- QProperty/QBindable quite nice, but its own universe
- idea: bring QML technology to C++
- can't roll it out to our own properties
- so we didn't
- akward: both technologies co-exist in QML now
- assumption: very few people are using it (to its full extent)
- if we want to remove it in Qt 7, we need to deprecate it
- bringing that up caused outcry:
- We need an equally way to expose properties
- only used for that?
- some ideas were brought up to fix it
- for instance a mega-marco which creates "everything" for a simple property
- With such a marco, could we deprecate properties?
- what about change signals, not wanting a change signal
- need a search&replace way to move forward
- devil's advocate: it's not only about boilerplate, people also want to have the reactive properties
- no public API to have setters with custom logic; real world use cases not covered by
- would people actually use it then?
- actual real world use cases were listed, multiple C++ QProperty; one Q_PROPERTY whose getter uses them, exposed to QML
-> update one C++ property results in automatic propagation to QML
- there are already libraries out there for saving boilerplate
- remove bindable from QObject, still keep QProperty as a class, but provide a bridge to QML?
- what do we tell our users then? How would it like in the end?
- In QObject: only signals, no BINDABLE
- But internal logic can still use QProperty
- Side questions: Concept of a property in other language (in the Qt brigdes context)?
- we want to use the native property support in other languages
- signals on value types discussion / discussion about value types