summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2020-08-21 22:31:39 +0200
committerFabian Kosmale <fabian.kosmale@qt.io>2020-09-01 17:59:36 +0200
commit11b8c46d2acf5421a8c57c02a55c3a36a11cf4f2 (patch)
tree1f375d05a49735e263440f6aa9d0cdb5f5d9c599 /src
parent3e7c63955e79dd70941df784d429c6dfab55be88 (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')
-rw-r--r--src/corelib/global/qnamespace.h3
-rw-r--r--src/corelib/global/qnamespace.qdoc7
-rw-r--r--src/corelib/kernel/qobject.cpp115
-rw-r--r--src/corelib/kernel/qobject_p.h6
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()
{