QtCS25 - QProperty and QBindable

From Qt Wiki
Jump to navigation Jump to search

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

  1. 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
  2. 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.
  3. Deprecate QObjectBindableProperty and friends.
  4. 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.
  5. Remove the integration with QProperty and QBindable in QML. QML will always use the READ/WRITE/NOTIFY methods again and ignore any BINDABLE.
  6. 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