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>
This commit is contained in:
Giuseppe D'Angelo 2020-08-21 22:31:39 +02:00 committed by Fabian Kosmale
parent 3e7c63955e
commit 11b8c46d2a
5 changed files with 680 additions and 42 deletions

View File

@ -1301,7 +1301,8 @@ namespace Qt {
DirectConnection,
QueuedConnection,
BlockingQueuedConnection,
UniqueConnection = 0x80
UniqueConnection = 0x80,
SingleShotConnection = 0x100,
};
enum ShortcutContext {

View File

@ -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

View File

@ -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.

View File

@ -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()
{

View File

@ -49,6 +49,8 @@
#include <private/qobject_p.h>
#endif
#include <functional>
#include <math.h>
class tst_QObject : public QObject
@ -154,6 +156,7 @@ private slots:
void nullReceiver();
void functorReferencesConnection();
void disconnectDisconnects();
void singleShotConnection();
};
struct QObjectCreatedOnShutdown
@ -7495,6 +7498,594 @@ void tst_QObject::disconnectDisconnects()
QCOMPARE(count, 3); // + δ
}
class ReceiverDisconnecting : public QObject
{
Q_OBJECT
public:
SenderObject *sender;
int slotCalledCount = 0;
public slots:
void aSlotByName()
{
++slotCalledCount;
QVERIFY(!disconnect(sender, SIGNAL(signal1()), this, SLOT(aSlotByName())));
}
void aSlotByPtr()
{
++slotCalledCount;
QVERIFY(!disconnect(sender, &SenderObject::signal1, this, &ReceiverDisconnecting::aSlotByPtr));
}
};
class DeleteThisReceiver : public QObject
{
Q_OBJECT
public:
static int counter;
public slots:
void deleteThis()
{
++counter;
delete this;
}
};
int DeleteThisReceiver::counter = 0;
void tst_QObject::singleShotConnection()
{
{
// Non single shot behavior: slot called every time the signal is emitted
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot);
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 3);
}
{
// Non single shot behavior: multiple connections cause multiple invocations
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot);
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 2);
QMetaObject::Connection c2 = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot);
QVERIFY(c);
QVERIFY(c2);
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QVERIFY(c);
QVERIFY(c2);
QCOMPARE(sender.aPublicSlotCalled, 4);
sender.emitSignal1();
QVERIFY(c);
QVERIFY(c2);
QCOMPARE(sender.aPublicSlotCalled, 6);
}
{
// Single shot behavior: slot called only once
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 1);
}
{
// Same, without holding a Connection object
SenderObject sender;
bool ok = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
}
{
// Single shot, disconnect before emitting
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
QVERIFY(QObject::disconnect(c));
QVERIFY(!c);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
}
{
// Single shot together with another connection
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot));
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 4);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 5);
}
{
// Two single shot, from the same signal, to the same slot
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
}
{
// Two single shot, from different signals, to the same slot
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QVERIFY(connect(&sender, &SenderObject::signal2,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal2();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal2();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
}
{
// Same signal, different connections
SenderObject sender;
ReceiverObject receiver1, receiver2;
receiver1.reset();
receiver2.reset();
QVERIFY(connect(&sender, &SenderObject::signal1,
&receiver1, &ReceiverObject::slot1));
QVERIFY(connect(&sender, &SenderObject::signal1,
&receiver2, &ReceiverObject::slot1,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QCOMPARE(receiver1.count_slot1, 0);
QCOMPARE(receiver2.count_slot1, 0);
sender.emitSignal1();
QCOMPARE(receiver1.count_slot1, 1);
QCOMPARE(receiver2.count_slot1, 1);
sender.emitSignal1();
QCOMPARE(receiver1.count_slot1, 2);
QCOMPARE(receiver2.count_slot1, 1);
sender.emitSignal1();
QCOMPARE(receiver1.count_slot1, 3);
QCOMPARE(receiver2.count_slot1, 1);
// Reestablish a single shot
QVERIFY(connect(&sender, &SenderObject::signal1,
&receiver2, &ReceiverObject::slot1,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QCOMPARE(receiver1.count_slot1, 3);
QCOMPARE(receiver2.count_slot1, 1);
sender.emitSignal1();
QCOMPARE(receiver1.count_slot1, 4);
QCOMPARE(receiver2.count_slot1, 2);
sender.emitSignal1();
QCOMPARE(receiver1.count_slot1, 5);
QCOMPARE(receiver2.count_slot1, 2);
}
{
// Check that the slot is invoked with the connection already disconnected
SenderObject sender;
QMetaObject::Connection c;
auto breakSlot = [&]() {
QVERIFY(!c);
++sender.aPublicSlotCalled;
};
c = connect(&sender, &SenderObject::signal1,
&sender, breakSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
QVERIFY(!c);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
QVERIFY(!c);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
QVERIFY(!c);
}
{
// Same
SenderObject sender;
ReceiverDisconnecting receiver;
receiver.sender = &sender;
bool ok = connect(&sender, SIGNAL(signal1()),
&receiver, SLOT(aSlotByName()),
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(receiver.slotCalledCount, 0);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 1);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 1);
// reconnect
ok = connect(&sender, SIGNAL(signal1()),
&receiver, SLOT(aSlotByName()),
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(receiver.slotCalledCount, 1);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 2);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 2);
}
{
// Same
SenderObject sender;
ReceiverDisconnecting receiver;
receiver.sender = &sender;
bool ok = connect(&sender, &SenderObject::signal1,
&receiver, &ReceiverDisconnecting::aSlotByPtr,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(receiver.slotCalledCount, 0);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 1);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 1);
// reconnect
ok = connect(&sender, &SenderObject::signal1,
&receiver, &ReceiverDisconnecting::aSlotByPtr,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(receiver.slotCalledCount, 1);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 2);
sender.emitSignal1();
QCOMPARE(receiver.slotCalledCount, 2);
}
{
// Reconnect from inside the slot
SenderObject sender;
std::function<void()> reconnectingSlot;
bool reconnect = false;
reconnectingSlot = [&]() {
++sender.aPublicSlotCalled;
if (reconnect) {
QObject::connect(&sender, &SenderObject::signal1,
&sender, reconnectingSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
}
};
bool ok = connect(&sender, &SenderObject::signal1,
&sender, reconnectingSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 1);
reconnect = true;
ok = connect(&sender, &SenderObject::signal1,
&sender, reconnectingSlot,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
QVERIFY(ok);
QCOMPARE(sender.aPublicSlotCalled, 1);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 2);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 3);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 4);
reconnect = false;
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 5);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 5);
}
{
// Delete the receiver from inside the slot
SenderObject sender;
QPointer<DeleteThisReceiver> p = new DeleteThisReceiver;
DeleteThisReceiver::counter = 0;
QVERIFY(connect(&sender, &SenderObject::signal1,
p.get(), &DeleteThisReceiver::deleteThis,
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection)));
QVERIFY(p);
QCOMPARE(DeleteThisReceiver::counter, 0);
sender.emitSignal1();
QCOMPARE(DeleteThisReceiver::counter, 1);
QVERIFY(!p);
sender.emitSignal1();
QCOMPARE(DeleteThisReceiver::counter, 1);
QVERIFY(!p);
}
{
// Queued, non single shot
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection)));
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
QTRY_COMPARE(sender.aPublicSlotCalled, 3);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 3);
QTRY_COMPARE(sender.aPublicSlotCalled, 4);
}
{
// Queued, single shot
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
QTRY_COMPARE(sender.aPublicSlotCalled, 1);
QTest::qWait(0);
QCOMPARE(sender.aPublicSlotCalled, 1);
}
{
// Queued, single shot, checking the connection handle
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
QTRY_COMPARE(sender.aPublicSlotCalled, 1);
QVERIFY(!c);
QTest::qWait(0);
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 1);
}
{
// Queued, single shot, disconnect before emitting
SenderObject sender;
QVERIFY(connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
QCOMPARE(sender.aPublicSlotCalled, 0);
QVERIFY(QObject::disconnect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot));
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QCOMPARE(sender.aPublicSlotCalled, 0);
QTest::qWait(0);
QCOMPARE(sender.aPublicSlotCalled, 0);
}
{
// Queued, single shot, disconnect before emitting by using the connection handle
SenderObject sender;
QMetaObject::Connection c = connect(&sender, &SenderObject::signal1,
&sender, &SenderObject::aPublicSlot,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection));
QVERIFY(c);
QCOMPARE(sender.aPublicSlotCalled, 0);
QVERIFY(QObject::disconnect(c));
QVERIFY(!c);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
sender.emitSignal1();
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
QTest::qWait(0);
QVERIFY(!c);
QCOMPARE(sender.aPublicSlotCalled, 0);
}
{
// Queued, single shot, delete the receiver from inside the slot
SenderObject sender;
QPointer<DeleteThisReceiver> p = new DeleteThisReceiver;
DeleteThisReceiver::counter = 0;
QVERIFY(connect(&sender, &SenderObject::signal1,
p.get(), &DeleteThisReceiver::deleteThis,
static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::SingleShotConnection)));
QCOMPARE(DeleteThisReceiver::counter, 0);
sender.emitSignal1();
QVERIFY(p);
QCOMPARE(DeleteThisReceiver::counter, 0);
sender.emitSignal1();
QVERIFY(p);
QCOMPARE(DeleteThisReceiver::counter, 0);
sender.emitSignal1();
QVERIFY(p);
QCOMPARE(DeleteThisReceiver::counter, 0);
QTRY_COMPARE(DeleteThisReceiver::counter, 1);
QVERIFY(!p);
QTest::qWait(0);
QCOMPARE(DeleteThisReceiver::counter, 1);
QVERIFY(!p);
}
}
// Test for QtPrivate::HasQ_OBJECT_Macro
static_assert(QtPrivate::HasQ_OBJECT_Macro<tst_QObject>::Value);
static_assert(!QtPrivate::HasQ_OBJECT_Macro<SiblingDeleter>::Value);