From 11b8c46d2acf5421a8c57c02a55c3a36a11cf4f2 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Fri, 21 Aug 2020 22:31:39 +0200 Subject: QObject: add a single shot connection flag If one needed to listen to a signal just once, one had to store the QMetaObject::Connection object returned by connect() and use it to disconnect the slot after the first signal activation. This has led to a proliferation of using wrappers (and enough TMP); they usually look like this: 1) create a shared_ptr, allocating its payload; 2) create a lambda, capturing the shared_ptr by value; 3) in the lambda, disconnect the connection (through the shared_ptr), and call the actual slot; 4) connect the signal to the lambda, storing the returned QMO::Connection into the shared_ptr. This is expensive, error prone for newcomers, and tricky to support as a general facility inside one's projects. We can do better, just support single shot connections right in QObject. [ChangeLog][QtCore][QObject] Added the Qt::SingleShotConnection flag. When a connection is established with this flag set, the slot is going to be activated at most once; when the signal is emitted, the connection gets automatically broken by Qt. Change-Id: I5f5feeae7f76c9c3d6323d841efba81c8f98ce7e Fixes: QTBUG-44219 Reviewed-by: Lars Knoll Reviewed-by: Fabian Kosmale Reviewed-by: Qt CI Bot Reviewed-by: Oswald Buddenhagen --- src/corelib/kernel/qobject.cpp | 115 +++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 39 deletions(-) (limited to 'src/corelib/kernel/qobject.cpp') diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp index 6cc0a9a4e7..4d0523c898 100644 --- a/src/corelib/kernel/qobject.cpp +++ b/src/corelib/kernel/qobject.cpp @@ -3325,8 +3325,14 @@ QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender, c2 = c2->nextConnectionList.loadRelaxed(); } } - type &= Qt::UniqueConnection - 1; } + type &= ~Qt::UniqueConnection; + + const bool isSingleShot = type & Qt::SingleShotConnection; + type &= ~Qt::SingleShotConnection; + + Q_ASSERT(type >= 0); + Q_ASSERT(type <= 3); std::unique_ptr c{new QObjectPrivate::Connection}; c->sender = s; @@ -3341,6 +3347,7 @@ QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender, c->isSlotObject = false; c->argumentTypes.storeRelaxed(types); c->callFunction = callFunction; + c->isSingleShot = isSingleShot; QObjectPrivate::get(s)->addConnection(signal_index, c.get()); @@ -3632,7 +3639,8 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect ++nargs; QBasicMutexLocker locker(signalSlotLock(c->receiver.loadRelaxed())); - if (!c->receiver.loadRelaxed()) { + QObject *receiver = c->receiver.loadRelaxed(); + if (!receiver) { // the connection has been disconnected before we got the lock return; } @@ -3658,17 +3666,22 @@ static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connect args[n] = types[n].create(argv[n]); } + if (c->isSingleShot && !QObjectPrivate::disconnect(c)) { + delete ev; + return; + } + locker.relock(); if (c->isSlotObject) c->slotObj->destroyIfLastRef(); - if (!c->receiver.loadRelaxed()) { + if (!c->isSingleShot && !c->receiver.loadRelaxed()) { // the connection has been disconnected while we were unlocked locker.unlock(); delete ev; return; } - QCoreApplication::postEvent(c->receiver.loadRelaxed(), ev); + QCoreApplication::postEvent(receiver, ev); } template @@ -3762,10 +3775,14 @@ void doActivate(QObject *sender, int signal_index, void **argv) sender->metaObject()->className(), sender, receiver->metaObject()->className(), receiver); } + + if (c->isSingleShot && !QObjectPrivate::disconnect(c)) + continue; + QSemaphore semaphore; { QBasicMutexLocker locker(signalSlotLock(receiver)); - if (!c->receiver.loadAcquire()) + if (!c->isSingleShot && !c->receiver.loadAcquire()) continue; QMetaCallEvent *ev = c->isSlotObject ? new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) : @@ -3778,6 +3795,9 @@ void doActivate(QObject *sender, int signal_index, void **argv) #endif } + if (c->isSingleShot && !QObjectPrivate::disconnect(c)) + continue; + QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index); if (c->isSlotObject) { @@ -4855,7 +4875,7 @@ QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signa */ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index, const QObject *receiver, void **slot, - QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type, + QtPrivate::QSlotObjectBase *slotObj, int type, const int *types, const QMetaObject *senderMetaObject) { if (!sender || !receiver || !slotObj || !senderMetaObject) { @@ -4889,8 +4909,14 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s c2 = c2->nextConnectionList.loadRelaxed(); } } - type = static_cast(type ^ Qt::UniqueConnection); } + type &= ~Qt::UniqueConnection; + + const bool isSingleShot = type & Qt::SingleShotConnection; + type &= ~Qt::SingleShotConnection; + + Q_ASSERT(type >= 0); + Q_ASSERT(type <= 3); std::unique_ptr c{new QObjectPrivate::Connection}; c->sender = s; @@ -4906,6 +4932,7 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s c->argumentTypes.storeRelaxed(types); c->ownArgumentTypes = false; } + c->isSingleShot = isSingleShot; QObjectPrivate::get(s)->addConnection(signal_index, c.get()); QMetaObject::Connection ret(c.release()); @@ -4929,39 +4956,12 @@ QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int s bool QObject::disconnect(const QMetaObject::Connection &connection) { QObjectPrivate::Connection *c = static_cast(connection.d_ptr); - - if (!c) - return false; - QObject *receiver = c->receiver.loadRelaxed(); - if (!receiver) - return false; - - QBasicMutex *senderMutex = signalSlotLock(c->sender); - QBasicMutex *receiverMutex = signalSlotLock(receiver); - - QObjectPrivate::ConnectionData *connections; - { - QOrderedMutexLocker locker(senderMutex, receiverMutex); - - // load receiver once again and recheck to ensure nobody else has removed the connection in the meantime - receiver = c->receiver.loadRelaxed(); - if (!receiver) - return false; - - connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed(); - Q_ASSERT(connections); - connections->removeConnection(c); + const bool disconnected = QObjectPrivate::disconnect(c); + if (disconnected) { + const_cast(connection).d_ptr = nullptr; + c->deref(); // has been removed from the QMetaObject::Connection object } - - connections->cleanOrphanedConnections(c->sender); - - c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(), - c->signal_index)); - - const_cast(connection).d_ptr = nullptr; - c->deref(); // has been removed from the QMetaObject::Connection object - - return true; + return disconnected; } /*! \fn template bool QObject::disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method) @@ -5084,6 +5084,43 @@ bool QObjectPrivate::disconnect(const QObject *sender, int signal_index, void ** return QMetaObjectPrivate::disconnect(sender, signal_index, senderMetaObject, sender, -1, slot); } +/*! + \internal + \threadsafe +*/ +bool QObjectPrivate::disconnect(QObjectPrivate::Connection *c) +{ + if (!c) + return false; + QObject *receiver = c->receiver.loadRelaxed(); + if (!receiver) + return false; + + QBasicMutex *senderMutex = signalSlotLock(c->sender); + QBasicMutex *receiverMutex = signalSlotLock(receiver); + + QObjectPrivate::ConnectionData *connections; + { + QOrderedMutexLocker locker(senderMutex, receiverMutex); + + // load receiver once again and recheck to ensure nobody else has removed the connection in the meantime + receiver = c->receiver.loadRelaxed(); + if (!receiver) + return false; + + connections = QObjectPrivate::get(c->sender)->connections.loadRelaxed(); + Q_ASSERT(connections); + connections->removeConnection(c); + } + + connections->cleanOrphanedConnections(c->sender); + + c->sender->disconnectNotify(QMetaObjectPrivate::signal(c->sender->metaObject(), + c->signal_index)); + + return true; +} + /*! \class QMetaObject::Connection \inmodule QtCore Represents a handle to a signal-slot (or signal-functor) connection. -- cgit v1.2.3