QtCS25 - QProperty and QBindable: Difference between revisions
(Created page with "==Session Summary== ''Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.'' ==Session Owners== ''Lorem Ipsum'' ''Consetetur Sadipscing'' ==Notes== ''Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At v...") |
Ulf Hermann (talk | contribs) |
||
| (3 intermediate revisions by 3 users 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== | ||
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 | |||
[[Category:QtCS2025]] | |||
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