Reputation: 76519
I have the habit of writing my "propertyChanged" signal
s with an argument, such that the receiving end doesn't need to call the Q_PROPERTY
's READ
function explicitly.
I do this out of clarity and the assumption that in a QML data binding situation, no "expensive" call to the getter needs to be done to actually fetch the value, as it's already passed to QML as a signal argument.
My colleagues disagreed and said it was against "QML style", to which I responded the documentation clearly states it may have an argument that will take the new value of the underlying member:
NOTIFY
signals forMEMBER
variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property.
Nowhere in the documentation is it stated that the QML binding system uses this parameter to prevent an additional function call to the getter when handling the signal. I understand this call will probably be made from C++, so no "expensive" QML to C++ call will be made, but it still is an extra function call, which in principle could result in a visible performance penalty in case of many updates.
I tried inspecting the QML binding source code, but couldn't infer anything from it. I wonder if someone knows what the deal is: is the signal argument used or not?
Upvotes: 8
Views: 11255
Reputation: 2734
Note that the code, generated in QtCreator by rightclicking on a property foo
and selecting "Refactor/Generate Missing Q_PROPERTY Members"
Q_PROPERTY(bool foo READ foo NOTIFY fooChanged)
generates a signal containing the property
void fooChanged(bool foo);
So I would argue that this is the Qt/QML style
Upvotes: 0
Reputation: 13691
Passing the values of the changed properties in the onPropertyChanged
-signal is, though possible, most surely not the QML style.
Would it be, then you should expect that at least for the basic types it is implemented, which is easily shown, it's not.
basictypes.qml
import QtQuick 2.7
import QtQuick.Controls 2.0
ApplicationWindow {
id: root
visible: true
width: 400; height: 450
property int num: 5
Button {
text: num
onClicked: num += 1
}
onNumChanged: console.log(JSON.stringify(arguments), arguments.length)
}
As you can see in the output, that there are no arguments passed, when you change even one of the most basic types, such as int
.
If now QML would use the optional, but rarely implemented passed value this would create overhead, as you would always need to check the existence of the argument before using it. Though a simple check is not to expensive, if it usually evaluates to false
, and then you use the workaround, why do it beforehand?
Though I might not rule out, that there are any passed values in any onPropertyChanged
-signals in the official realse, there are none for properties added in QML with property [type] [name]
. There also none for most inherited properties (tested the Button: text
, width
, height
).
Upvotes: 2
Reputation: 24396
I wonder if someone knows what the deal is: is the signal argument used or not?
Here's one way to check:
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QDebug>
class MyType : public QObject
{
Q_OBJECT
Q_PROPERTY(bool foo READ foo WRITE setFoo NOTIFY fooChanged)
public:
MyType(QObject *parent = nullptr) :
QObject(parent),
mFoo(0)
{
}
bool foo() const
{
qDebug() << Q_FUNC_INFO;
return mFoo;
}
void setFoo(bool foo)
{
if (foo == mFoo)
return;
mFoo = foo;
emit fooChanged(mFoo);
}
signals:
void fooChanged(bool foo);
private:
bool mFoo;
};
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
qmlRegisterType<MyType>("App", 1, 0, "MyType");
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
return app.exec();
}
#include "main.moc"
main.qml:
import QtQuick 2.0
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import App 1.0
Window {
width: 400
height: 400
visible: true
Switch {
id: fooSwitch
}
MyType {
id: myType
foo: fooSwitch.checked
onFooChanged: print("onFooChanged, foo =", foo)
// onFooChanged: print("onFooChanged myType.foo =", myType.foo)
}
}
The output when switching back and forth is:
qml: onFooChanged, foo = true
qml: onFooChanged, foo = false
So it's safe to say that the value is used and not the getter.
To see what the output would have been like if the signal argument hadn't been used, uncomment the commented out line and comment out the other one:
bool __cdecl MyType::foo(void) const
qml: onFooChanged myType.foo = true
bool __cdecl MyType::foo(void) const
qml: onFooChanged myType.foo = false
Upvotes: 4
Reputation: 49289
assumption that in a QML data binding situation, no "expensive" call to the getter needs to be done to actually fetch the value, as it's already passed to QML as a signal argument.
Technically speaking, it is not likely that there is anything similar to what you describe. It just doesn't make sense. If your getters are expensive, you should take care to cache the result in a simpler form, and either update on changes or on demand.
If QML bindings were exclusively single property to single property, such an optimization could make sense. But bindings in QML are actually anonymous expressions, and the way it works is when any change notification for an object that is referenced by the expression triggers its reevaluation. In such a case it would add needless complexity to the implementation, having one value sent from the notification signal to keep and others which you have to call getters for.
Obviously, I am just postulating here. Another thing that makes me skeptic of the existence of such an optimization is that the property binding system isn't all that sophisticated or focused on efficiency. Which is evident from the fact that it cannot even eliminate redundant evaluations in the case of property value inter-dependencies. I've seen claims that such optimizations exist, but I've tested and it failed to avoid even the most simple of binding tree redundancies.
Naturally, if you insist on MEMBER
properties, this is not so easy to prove, since the getters are auto generated, and you don't get to insert your debug statement there.
But if you try it for a regular property, you will find out that even if the signal emits the actual value, the getter is invoked nonetheless. There is absolutely no reason why an auto-generated getter would get a different treatment.
As mentioned in the linked answer, if you need the value emitted for the C++ part of the code, then keep, it it will not interfere with the QML part. If not, then simply don't emit the value, even if minuscule, it is still overhead. Emitting the value is the C++ way, bindings are a fundamentally different concept that is not really applicable in C++ (not without extensive verbosity), the QML way does not require to emit the changed value.
So your are both wrong. It is not really "against the QML style", as it will not hinder anything, and having the option to emit a value in the documentation in no way suggest that it is "in line with the QML style", or that it gets any special treatment. So if that's the reason you are doing it, you might as well stop, as it is entirely redundant.
Upvotes: 2