[Development] RFC: RAII for property changes
André Somers
andre at familiesomers.nl
Wed Apr 15 11:49:56 CEST 2015
Hi,
When writing QObject-derived classes with properties, I often found
myself writing code like this:
void MyClass::setFoo(QString value)
{
if (m_foo != value) {
m_foo = value;
emit fooChanged(m_foo);
}
}
Trivial enough, in the simple case. However, when more than one property
is involved at the same time, or properties depend on each other, things
can get way less simple and more error-phrone quickly, I found. If the
order of signals matters (and I found it does some times!), it gets even
more tricky to get things right. This is especially tricky because you
really would like to emit the signal at the moment the class is in a
consistent state. That is, if you have a method that sets both width and
height of an object at the same time, you really don't want to emit the
signal about the change of one property before the change of another
property is also set. In this case, the change may be harmless (but
potentially expensive, as perhaps two relayouts are triggered when only
one was needed), but there are cases where you'd really expose the class
in an inconsistent state if you emit too soon. Considder that at the
moment you emit, you give other code the change to do whatever its wants
again, including calling methods on your class, even the very same
method they just called, or worse, delete it... Your class better be in
a consistent state when they do such things.
That's why I have been working with a RAII implementation to monitor
property changes for a while now. The idea is that you instantiate a
RAII class to monitor a property at the beginning of a method that _may_
change the property value, and then do all you want. Upon destruction of
the RAII class, it checks if the property value changed and if it did,
emit the associated signal.
The code I now write looks like this:
void MyClass::setFoo(QString value)
{
PropertyGuard guard(this, "foo"); //foo is the name of the Q_PROPERTY
Q_UNUSED(guard);
m_foo = value;
}
which then is further simplified by creating a small macro called
guardProperty to this:
void MyClass::setFoo(QString value)
{
guardProperty(foo); //foo is the name of the Q_PROPERTY
m_foo = value;
}
Note that you can guard as many properties as you need:
void MyClass::setFoo(QString value)
{
guardProperty(foo); //foo is the name of the Q_PROPERTY
guardProperty(isValid); //the isValid property depends on m_foo as well
m_foo = value;
}
The PropertyGuard class itself is quite simple in its basic form. It
uses QMetaObject to read the current value and find the notify signal
for the property. Upon desctruction, it compares the current value to
the initial value stored at creation, and if the two don't match, the
signal is emitted. The code deals with signals with zero or one
arguments, assuming that in the latter case the one argument is the new
property value.
I found this little device to be very useful in practice. Not so much in
trivial setters like the ones on top (but still useful there, as the
intent is clearer and there is less typing), but very much so in
non-trivial cases where more than one property may change at a time or
other complex interactions take place. In cases where a method changing
should trigger other property changes as well, while not exposing
inconsistent states of the class I found the device to be extremely
valuable, as I can be sure that the emits are done at a moment where the
state of class is fully consistent. I find myself writing emit less and
less.
So, what are your opinions on using a mechanism like this? I have not
found it in Qt itself, but perhaps others here are already using similar
methods or perhaps very different methods to tackle the issues I described?
Looking forward to your comments,
André Somers
More information about the Development
mailing list