diff options
author | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2020-08-21 22:31:39 +0200 |
---|---|---|
committer | Fabian Kosmale <fabian.kosmale@qt.io> | 2020-09-01 17:59:36 +0200 |
commit | 11b8c46d2acf5421a8c57c02a55c3a36a11cf4f2 (patch) | |
tree | 1f375d05a49735e263440f6aa9d0cdb5f5d9c599 /src/corelib | |
parent | 3e7c63955e79dd70941df784d429c6dfab55be88 (diff) |
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<QMO::Connection>, 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 <lars.knoll@qt.io>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@gmx.de>
Diffstat (limited to 'src/corelib')
-rw-r--r-- | src/corelib/global/qnamespace.h | 3 | ||||
-rw-r--r-- | src/corelib/global/qnamespace.qdoc | 7 | ||||
-rw-r--r-- | src/corelib/kernel/qobject.cpp | 115 | ||||
-rw-r--r-- | src/corelib/kernel/qobject_p.h | 6 |
4 files changed, 89 insertions, 42 deletions
diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index 9b5af17f7c..a531bc627b 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -1301,7 +1301,8 @@ namespace Qt { DirectConnection, QueuedConnection, BlockingQueuedConnection, - UniqueConnection = 0x80 + UniqueConnection = 0x80, + SingleShotConnection = 0x100, }; enum ShortcutContext { diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index b5830173f8..95a73c4439 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -638,6 +638,13 @@ (i.e. if the same signal is already connected to the same slot for the same pair of objects). This flag was introduced in Qt 4.6. + \value SingleShotConnection + This is a flag that can be combined with any one of the above + connection types, using a bitwise OR. When Qt::SingleShotConnection + is set, the slot is going to be called only once; the connection + will be automatically broken when the signal is emitted. + This flag was introduced in Qt 6.0. + With queued connections, the parameters must be of types that are known to Qt's meta-object system, because Qt needs to copy the arguments to store them in an event behind the scenes. If you try 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<QObjectPrivate::Connection> 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 <bool callbacks_enabled> @@ -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<Qt::ConnectionType>(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<QObjectPrivate::Connection> 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<QObjectPrivate::Connection *>(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<QMetaObject::Connection &>(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<QMetaObject::Connection &>(connection).d_ptr = nullptr; - c->deref(); // has been removed from the QMetaObject::Connection object - - return true; + return disconnected; } /*! \fn template<typename PointerToMemberFunction> 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. diff --git a/src/corelib/kernel/qobject_p.h b/src/corelib/kernel/qobject_p.h index 4a60d37191..1f13274945 100644 --- a/src/corelib/kernel/qobject_p.h +++ b/src/corelib/kernel/qobject_p.h @@ -153,9 +153,10 @@ public: ushort method_offset; ushort method_relative; signed int signal_index : 27; // In signal range (see QObjectPrivate::signalIndex()) - ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking + ushort connectionType : 2; // 0 == auto, 1 == direct, 2 == queued, 3 == blocking ushort isSlotObject : 1; ushort ownArgumentTypes : 1; + ushort isSingleShot : 1; Connection() : ref_(2), ownArgumentTypes(true) { //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection } @@ -348,10 +349,11 @@ public: static QMetaObject::Connection 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); static QMetaObject::Connection connect(const QObject *sender, int signal_index, QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type); static bool disconnect(const QObject *sender, int signal_index, void **slot); + static bool disconnect(Connection *c); void ensureConnectionData() { |