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 --- tests/auto/corelib/kernel/qobject/tst_qobject.cpp | 591 ++++++++++++++++++++++ 1 file changed, 591 insertions(+) (limited to 'tests') diff --git a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp index 1d274d8c95..01a847596e 100644 --- a/tests/auto/corelib/kernel/qobject/tst_qobject.cpp +++ b/tests/auto/corelib/kernel/qobject/tst_qobject.cpp @@ -49,6 +49,8 @@ #include #endif +#include + #include 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::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::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::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::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::SingleShotConnection))); + QVERIFY(connect(&sender, &SenderObject::signal1, + &sender, &SenderObject::aPublicSlot, + static_cast(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::SingleShotConnection))); + QVERIFY(connect(&sender, &SenderObject::signal2, + &sender, &SenderObject::aPublicSlot, + static_cast(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::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::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::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::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::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::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::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 reconnectingSlot; + bool reconnect = false; + reconnectingSlot = [&]() { + ++sender.aPublicSlotCalled; + if (reconnect) { + QObject::connect(&sender, &SenderObject::signal1, + &sender, reconnectingSlot, + static_cast(Qt::SingleShotConnection)); + } + }; + + bool ok = connect(&sender, &SenderObject::signal1, + &sender, reconnectingSlot, + static_cast(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::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 p = new DeleteThisReceiver; + DeleteThisReceiver::counter = 0; + + QVERIFY(connect(&sender, &SenderObject::signal1, + p.get(), &DeleteThisReceiver::deleteThis, + static_cast(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::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::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::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::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::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 p = new DeleteThisReceiver; + DeleteThisReceiver::counter = 0; + + QVERIFY(connect(&sender, &SenderObject::signal1, + p.get(), &DeleteThisReceiver::deleteThis, + static_cast(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::Value); static_assert(!QtPrivate::HasQ_OBJECT_Macro::Value); -- cgit v1.2.3