Document in a test that mixing connect/disconnect syntax doesn't work

We cannot go from member function pointer to method index for slots (we
can for signals), so using string-based syntax to connect and PMF-syntax
to disconnect doesn't work, and vice versa.

Document this in a (partially failing) test, and add a warning to the
relevant QObject::disconnect documentation.

Task-number: QTBUG-126580
Task-number: QTBUG-126581
Change-Id: I4b17662aa9aa7b624049c5b0a3b046ed35230f05
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
(cherry picked from commit 27a3229626249a100d8e6fa495927715aba6963d)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Volker Hilsheimer 2024-06-25 12:01:42 +02:00 committed by Qt Cherry-pick Bot
parent 0fb43e4395
commit f715325932
3 changed files with 143 additions and 1 deletions

View File

@ -8,3 +8,12 @@ affect classes that rely on this signal for cleaning up resources. It is
recommended to disconnect only the specific signals that were connected by
application code.
//! [disconnect-all]
//! [disconnect-mismatch]
\note Use the the same syntax, pointer-to-member-function or string-based using
the \c SIGNAL and \c SLOT macros, in connect() and corresponding disconnect()
calls.
To avoid mismatches, store the connection handle returned by connect(), and use
it in the call to \l{disconnect(const QMetaObject::Connection &connection)}{disconnect()}.
//! [disconnect-mismatch]

View File

@ -3210,6 +3210,8 @@ QMetaObject::Connection QObject::connect(const QObject *sender, const QMetaMetho
\endlist
\include includes/qobject.qdocinc disconnect-mismatch
\nullptr may be used as a wildcard, meaning "any signal", "any receiving
object", or "any slot in the receiving object", respectively.
@ -3362,6 +3364,8 @@ bool QObject::disconnect(const QObject *sender, const char *signal,
\endlist
\include includes/qobject.qdocinc disconnect-mismatch
QMetaMethod() may be used as wildcard in the meaning "any signal" or "any slot in receiving object".
In the same way \nullptr can be used for \a receiver in the meaning "any receiving object".
In this case method should also be QMetaMethod(). \a sender parameter should be never \nullptr.
@ -3435,6 +3439,8 @@ bool QObject::disconnect(const QObject *sender, const QMetaMethod &signal,
Disconnects \a signal from \a method of \a receiver.
\include includes/qobject.qdocinc disconnect-mismatch
A signal-slot connection is removed when either of the objects
involved are destroyed.
@ -3448,6 +3454,8 @@ bool QObject::disconnect(const QObject *sender, const QMetaMethod &signal,
Disconnects all signals in this object from \a receiver's \a
method.
\include includes/qobject.qdocinc disconnect-mismatch
A signal-slot connection is removed when either of the objects
involved are destroyed.
*/
@ -5384,7 +5392,12 @@ bool QObject::disconnect(const QMetaObject::Connection &connection)
\note It is not possible to use this overload to disconnect signals
connected to functors or lambda expressions. That is because it is not
possible to compare them. Instead, use the overload that takes a
QMetaObject::Connection
QMetaObject::Connection.
\note Unless \a method is \nullptr, this function will also not break
connections that were made using the string-based version of connect(). To
break such connections, use the corresponding string-based overload of
disconnect().
\sa connect()
*/

View File

@ -110,6 +110,7 @@ private slots:
void baseDestroyed();
void pointerConnect();
void pointerDisconnect();
void mixedConnectDisconnect();
void emitInDefinedOrderPointer();
void customTypesPointer();
void connectCxx0x();
@ -4852,6 +4853,125 @@ void tst_QObject::pointerDisconnect()
QVERIFY(!r2.called(2));
}
struct MixingFunctor
{
int index;
void operator()() { s_called[index] = true; }
bool called() const { return s_called[index]; }
void reset() { s_called[index] = false; }
private:
static inline bool s_called[5] = {};
};
void tst_QObject::mixedConnectDisconnect()
{
SenderObject s;
ReceiverObject pmf;
ReceiverObject named;
MixingFunctor functor1{1};
MixingFunctor functor2{2};
MixingFunctor functor3{3};
MixingFunctor functor4{4};
auto makePMFConnections = [sender = &s](const ReceiverObject *receiver){
QObject::connect(sender, &SenderObject::signal1, receiver, &ReceiverObject::slot1);
QObject::connect(sender, &SenderObject::signal2, receiver, &ReceiverObject::slot2);
QObject::connect(sender, &SenderObject::signal3, receiver, &ReceiverObject::slot3);
QObject::connect(sender, &SenderObject::signal4, receiver, &ReceiverObject::slot4);
};
auto makeNamedConnections = [sender = &s](const ReceiverObject *receiver){
QObject::connect(sender, SIGNAL(signal1()), receiver, SLOT(slot1()));
QObject::connect(sender, SIGNAL(signal2()), receiver, SLOT(slot2()));
QObject::connect(sender, SIGNAL(signal3()), receiver, SLOT(slot3()));
QObject::connect(sender, SIGNAL(signal4()), receiver, SLOT(slot4()));
};
makePMFConnections(&pmf);
makeNamedConnections(&named);
QObject::connect(&s, &SenderObject::signal1, &named, functor1);
QObject::connect(&s, &SenderObject::signal2, &named, functor2);
QObject::connect(&s, &SenderObject::signal3, &named, functor3);
QObject::connect(&s, &SenderObject::signal4, &named, functor4);
// sanity check
s.emitSignal1();
QVERIFY(pmf.called(1));
QVERIFY(named.called(1));
QVERIFY(functor1.called());
pmf.reset();
named.reset();
functor1.reset();
// disconnecting a string-based connection with PMF-based disconnect doesn't work
bool ret = false;
ret = QObject::disconnect(&s, &SenderObject::signal1, &pmf, &ReceiverObject::slot1);
QVERIFY(ret);
ret = QObject::disconnect(&s, &SenderObject::signal1, &named, &ReceiverObject::slot1);
QEXPECT_FAIL("", "Mixing PMF connect with string-based disconnect doesn't work", Continue);
QVERIFY(ret);
s.emitSignal1();
QVERIFY(!pmf.called(1));
QEXPECT_FAIL("", "Mixing PMF connect with string-based disconnect doesn't work", Continue);
QVERIFY(!named.called(1));
// cannot disconnect a specific functor (or lambda)
QVERIFY(functor1.called());
pmf.reset();
named.reset();
functor1.reset();
// wildcard disconnect works even in mixed cases and for functor objects
ret = QObject::disconnect(&s, SIGNAL(signal2()), &pmf, nullptr);
QVERIFY(ret);
ret = QObject::disconnect(&s, &SenderObject::signal2, &named, nullptr);
QVERIFY(ret);
s.emitSignal2();
QVERIFY(!pmf.called(2));
QVERIFY(!named.called(2));
QVERIFY(!functor2.called());
pmf.reset();
named.reset();
functor2.reset();
ret = QObject::disconnect(&s, &SenderObject::signal3, nullptr, nullptr);
QVERIFY(ret);
s.emitSignal3();
QVERIFY(!pmf.called(3));
QVERIFY(!named.called(3));
QVERIFY(!functor3.called());
pmf.reset();
named.reset();
functor3.reset();
// disconnect() member function is only available for string-based syntax
// and doesn't disconnect PMF-based connections
ret = s.disconnect(&pmf, SLOT(slot4()));
QEXPECT_FAIL("", "Mixing PMF connect with string-based disconnect doesn't work", Continue);
QVERIFY(ret);
s.emitSignal4();
QEXPECT_FAIL("", "Mixing PMF connect with string-based disconnect doesn't work", Continue);
QVERIFY(!pmf.called(4));
// the functor gets of course still called
QVERIFY(functor4.called());
pmf.reset();
named.reset();
functor4.reset();
ret = s.disconnect(&named, nullptr);
QVERIFY(ret);
s.emitSignal4();
QVERIFY(!named.called(4));
QVERIFY(!functor4.called());
named.reset();
functor4.reset();
}
void tst_QObject::emitInDefinedOrderPointer()
{