Long live QScopedPropertyUpdateGroup

A RAII wrapper around Qt::{begin,end}PropertyUpdateGroup().

[ChangeLog][QtCore][QScopedPropertyUpdateGroup] New RAII class
wrapping Qt::beginPropertyUpdateGroup() and
Qt::endPropertyUpdateGroup().

Fixes: QTBUG-110710
Change-Id: If2619e9584dd9d57985d63e3babca75421499ab9
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
bb10
Marc Mutz 2023-02-05 19:14:58 +01:00
parent 48f0d458a9
commit 224930726b
5 changed files with 80 additions and 34 deletions

View File

@ -1121,7 +1121,7 @@ void QAbstractAnimation::setDirection(Direction direction)
return;
}
Qt::beginPropertyUpdateGroup();
const QScopedPropertyUpdateGroup guard;
const int oldCurrentLoop = d->currentLoop;
if (state() == Stopped) {
if (direction == Backward) {
@ -1148,7 +1148,6 @@ void QAbstractAnimation::setDirection(Direction direction)
if (d->currentLoop != oldCurrentLoop)
d->currentLoop.notify();
d->direction.notify();
Qt::endPropertyUpdateGroup();
}
QBindable<QAbstractAnimation::Direction> QAbstractAnimation::bindableDirection()

View File

@ -2582,7 +2582,7 @@ QBindable<QRegularExpression> QSortFilterProxyModel::bindableFilterRegularExpres
void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression &regularExpression)
{
Q_D(QSortFilterProxyModel);
Qt::beginPropertyUpdateGroup();
const QScopedPropertyUpdateGroup guard;
const bool regExpChanged = regularExpression != d->filter_regularexpression.value();
d->filter_regularexpression.removeBindingUnlessInWrapper();
d->filter_casesensitive.removeBindingUnlessInWrapper();
@ -2601,7 +2601,6 @@ void QSortFilterProxyModel::setFilterRegularExpression(const QRegularExpression
d->filter_regularexpression.notify();
if (cs != updatedCs)
d->filter_casesensitive.notify();
Qt::endPropertyUpdateGroup();
}
/*!
@ -2677,7 +2676,7 @@ void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs)
if (cs == d->filter_casesensitive)
return;
Qt::beginPropertyUpdateGroup();
const QScopedPropertyUpdateGroup guard;
QRegularExpression::PatternOptions options =
d->filter_regularexpression.value().patternOptions();
options.setFlag(QRegularExpression::CaseInsensitiveOption, cs == Qt::CaseInsensitive);
@ -2690,7 +2689,6 @@ void QSortFilterProxyModel::setFilterCaseSensitivity(Qt::CaseSensitivity cs)
d->filter_changed(QSortFilterProxyModelPrivate::Direction::Rows);
d->filter_regularexpression.notify();
d->filter_casesensitive.notify();
Qt::endPropertyUpdateGroup();
}
QBindable<Qt::CaseSensitivity> QSortFilterProxyModel::bindableFilterCaseSensitivity()

View File

@ -185,7 +185,7 @@ Q_CONSTINIT static thread_local QBindingStatus bindingStatus;
properties need to be updated, preventing any external observer from noticing an inconsistent
state.
\sa Qt::endPropertyUpdateGroup
\sa Qt::endPropertyUpdateGroup, QScopedPropertyUpdateGroup
*/
void Qt::beginPropertyUpdateGroup()
{
@ -205,7 +205,7 @@ void Qt::beginPropertyUpdateGroup()
\warning Calling endPropertyUpdateGroup without a preceding call to beginPropertyUpdateGroup
results in undefined behavior.
\sa Qt::beginPropertyUpdateGroup
\sa Qt::beginPropertyUpdateGroup, QScopedPropertyUpdateGroup
*/
void Qt::endPropertyUpdateGroup()
{
@ -239,6 +239,41 @@ void Qt::endPropertyUpdateGroup()
}
}
/*!
\since 6.6
\class QScopedPropertyUpdateGroup
\inmodule QtCore
\ingroup tools
\brief RAII class around Qt::beginPropertyUpdateGroup()/Qt::endPropertyUpdateGroup()
This class calls Qt::beginPropertyUpdateGroup() in its constructor and
Qt::endPropertyUpdateGroup() in its destructor, making sure the latter
function is reliably called even in the presence of early returns or thrown
exceptions.
\note Qt::endPropertyUpdateGroup() may re-throw exceptions thrown by
binding evaluations. This means your application may crash
(\c{std::terminate()} called) if another exception is causing
QScopedPropertyUpdateGroup's destructor to be called during stack
unwinding. If you expect exceptions from binding evaluations, use manual
Qt::endPropertyUpdateGroup() calls and \c{try}/\c{catch} blocks.
\sa QProperty
*/
/*!
\fn QScopedPropertyUpdateGroup::QScopedPropertyUpdateGroup()
Calls Qt::beginPropertyUpdateGroup().
*/
/*!
\fn QScopedPropertyUpdateGroup::~QScopedPropertyUpdateGroup()
Calls Qt::endPropertyUpdateGroup().
*/
// check everything stored in QPropertyBindingPrivate's union is trivially destructible
// (though the compiler would also complain if that weren't the case)
static_assert(std::is_trivially_destructible_v<QPropertyBindingSourceLocation>);

View File

@ -53,6 +53,16 @@ Q_CORE_EXPORT void beginPropertyUpdateGroup();
Q_CORE_EXPORT void endPropertyUpdateGroup();
}
class [[nodiscard]] QScopedPropertyUpdateGroup
{
Q_DISABLE_COPY_MOVE(QScopedPropertyUpdateGroup)
public:
QScopedPropertyUpdateGroup()
{ Qt::beginPropertyUpdateGroup(); }
~QScopedPropertyUpdateGroup() noexcept(false)
{ Qt::endPropertyUpdateGroup(); }
};
template <typename T>
class QPropertyData : public QUntypedPropertyData
{

View File

@ -1782,13 +1782,14 @@ void tst_QProperty::propertyAdaptorBinding()
QCOMPARE(object.fooChangedCount, 7);
// Check update group
Qt::beginPropertyUpdateGroup();
source.setValue(23);
source2.setValue(22);
QCOMPARE(object.foo(), 43);
QCOMPARE(dest1.value(), 43);
QCOMPARE(object.fooChangedCount, 7);
Qt::endPropertyUpdateGroup();
{
const QScopedPropertyUpdateGroup guard;
source.setValue(23);
source2.setValue(22);
QCOMPARE(object.foo(), 43);
QCOMPARE(dest1.value(), 43);
QCOMPARE(object.fooChangedCount, 7);
}
QCOMPARE(object.foo(), 45);
QCOMPARE(dest1.value(), 45);
QCOMPARE(object.fooChangedCount, 8);
@ -2085,27 +2086,29 @@ void tst_QProperty::groupedNotifications()
QCOMPARE(nNotifications, 1);
expected = 2;
Qt::beginPropertyUpdateGroup();
a = 1;
QCOMPARE(b.value(), 0);
QCOMPARE(c.value(), 0);
QCOMPARE(d.value(), 0);
QCOMPARE(nNotifications, 1);
Qt::endPropertyUpdateGroup();
{
const QScopedPropertyUpdateGroup guard;
a = 1;
QCOMPARE(b.value(), 0);
QCOMPARE(c.value(), 0);
QCOMPARE(d.value(), 0);
QCOMPARE(nNotifications, 1);
}
QCOMPARE(b.value(), 1);
QCOMPARE(c.value(), 1);
QCOMPARE(e.value(), 2);
QCOMPARE(nNotifications, 2);
expected = 7;
Qt::beginPropertyUpdateGroup();
a = 2;
d = 3;
QCOMPARE(b.value(), 1);
QCOMPARE(c.value(), 1);
QCOMPARE(d.value(), 3);
QCOMPARE(nNotifications, 2);
Qt::endPropertyUpdateGroup();
{
const QScopedPropertyUpdateGroup guard;
a = 2;
d = 3;
QCOMPARE(b.value(), 1);
QCOMPARE(c.value(), 1);
QCOMPARE(d.value(), 3);
QCOMPARE(nNotifications, 2);
}
QCOMPARE(b.value(), 2);
QCOMPARE(c.value(), 2);
QCOMPARE(e.value(), 7);
@ -2128,10 +2131,11 @@ void tst_QProperty::groupedNotificationConsistency()
j = 1;
QVERIFY(!areEqual); // value changed runs before j = 1
Qt::beginPropertyUpdateGroup();
i = 2;
j = 2;
Qt::endPropertyUpdateGroup();
{
const QScopedPropertyUpdateGroup guard;
i = 2;
j = 2;
}
QVERIFY(areEqual); // value changed runs after everything has been evaluated
}