Add a QMetaObject::connect function

This adds a QMetaObject::connect function allowing to connect a
QMetaMethod to a PMF or a functor. This is most useful when connecting
a signal retrieved by QMetaObject introspection to a lambda with
a capture. This aims to replace the remaining usecases of QSignalMapper
with a lighter alternative not relying on sender()/senderSignalIndex().
Note that this function doesn't support implicit conversion between
argument or return types.
It does support lambda with captures and signals returning values.

[ChangeLog][QtCore][QMetaObject] Added a QMetaObject::connect function
for connecting a signal's QMetaMethod to a member function or a functor
(lambdas, etc.)

Fixes: QTBUG-120631
Change-Id: Ifca73cd047478f6a262728493b16a942bff36e40
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
This commit is contained in:
Pierre-Yves Siret 2024-11-02 03:45:31 +01:00
parent aad0ab897f
commit d8b4eb1a17
3 changed files with 258 additions and 0 deletions

View File

@ -3910,6 +3910,96 @@ void QMetaObject::connectSlotsByName(QObject *o)
}
}
/*!
\fn template<typename PointerToMemberFunction> QMetaObject::Connection QMetaObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
\threadsafe
\overload connect()
\since 6.10
Creates a connection of the given \a type from the \a signal in
the \a sender object to the \a method in the \a receiver object.
Returns a handle to the connection that can be used to disconnect
it later.
The Connection handle will be invalid if it cannot create the
connection, for example, the parameters were invalid.
You can check if the QMetaObject::Connection is valid by casting
it to a bool.
Pass the returned handle to QObject::disconnect() to disconnect
the connection.
A slot can be connected to a given signal if the signal has at
least as many arguments as the slot. There must be an exact match
between the corresponding signal and slot arguments, implicit
conversions and type checking are not handled by this function.
Overloaded slots need to be explicitly be resolved with
help of \l qOverload.
\sa QObject::connect(), QObject::disconnect()
*/
/*!
\fn template<typename Functor> QMetaObject::Connection QMetaObject::connect(const QObject *sender, const QMetaMethod &signal, const QObject *context, Functor functor, Qt::ConnectionType type)
\threadsafe
\overload connect()
\since 6.10
Creates a connection of a given \a type from \a signal in
\a sender object to \a functor to be placed in a specific event
loop of \a context.
Returns a handle to the connection that can be used to disconnect
it later.
This can be useful for connecting a signal retrieved from
meta-object introspection to a lambda capturing local variables.
\note Qt::UniqueConnections do not work for lambdas, non-member
functions and functors; they only apply to member functions.
The slot function can be any function or functor with with equal
or fewer arguments than the signal. There must be an exact match
between the corresponding signal and slot arguments, implicit
conversions and type checking are not handled by this function.
Overloaded functors need to be explicitly be resolved with
help of \l qOverload.
The connection will automatically disconnect if the sender or
the context is destroyed.
However, you should take care that any objects used within
the functor are still alive when the signal is emitted.
\sa QObject::connect(), QObject::disconnect()
*/
QMetaObject::Connection QMetaObject::connectImpl(const QObject *sender, const QMetaMethod &signal,
const QObject *receiver, void **slot,
QtPrivate::QSlotObjectBase *slotObjRaw, Qt::ConnectionType type)
{
QtPrivate::SlotObjUniquePtr slotObj(slotObjRaw);
if (!signal.isValid() || signal.methodType() != QMetaMethod::Signal) {
qCWarning(lcConnect, "QObject::connect: invalid signal parameter");
return QMetaObject::Connection();
}
int signal_index;
{
int dummy;
QMetaObjectPrivate::memberIndexes(sender, signal, &signal_index, &dummy);
}
const QMetaObject *senderMetaObject = sender->metaObject();
if (signal_index == -1) {
qCWarning(lcConnect, "QObject::connect: Can't find signal %s on instance of class %s",
signal.methodSignature().constData(), senderMetaObject->className());
return QMetaObject::Connection();
}
return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj.release(), type, nullptr, senderMetaObject);
}
/*!
\internal
A small RAII helper for QSlotObjectBase.

View File

@ -292,6 +292,19 @@ struct Q_CORE_EXPORT QMetaObject
// internal slot-name based connect
static void connectSlotsByName(QObject *o);
#ifdef Q_QDOC
template<typename PointerToMemberFunction>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection);
template<typename PointerToMemberFunction, typename Functor>
static QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type = Qt::AutoConnection);
#else
template <typename Func>
static inline Connection
connect(const QObject *sender, const QMetaMethod &signal,
const typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *context, Func &&slot,
Qt::ConnectionType type = Qt::AutoConnection);
#endif // Q_QDOC
// internal index-based signal activation
static void activate(QObject *sender, int signal_index, void **argv);
static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
@ -647,6 +660,11 @@ private:
static QObject *newInstanceImpl(const QMetaObject *mobj, qsizetype parameterCount,
const void **parameters, const char **typeNames,
const QtPrivate::QMetaTypeInterface **metaTypes);
static QMetaObject::Connection connectImpl(const QObject *sender, const QMetaMethod& signal,
const QObject *receiver, void **slotPtr,
QtPrivate::QSlotObjectBase *slot, Qt::ConnectionType type);
friend class QTimer;
friend class QChronoTimer;
};
@ -677,6 +695,32 @@ public:
void swap(Connection &other) noexcept { qt_ptr_swap(d_ptr, other.d_ptr); }
};
template <typename Func>
QMetaObject::Connection
QMetaObject::connect(const QObject *sender, const QMetaMethod &signal,
const typename QtPrivate::ContextTypeForFunctor<Func>::ContextType *context, Func &&slot,
Qt::ConnectionType type)
{
using Slot = std::decay_t<Func>;
using FunctionSlotType = QtPrivate::FunctionPointer<Slot>;
void **pSlot = nullptr;
QtPrivate::QSlotObjectBase *slotObject;
if constexpr (FunctionSlotType::ArgumentCount != -1) {
slotObject = new QtPrivate::QCallableObject<Slot, typename FunctionSlotType::Arguments, typename FunctionSlotType::ReturnType>(std::forward<Func>(slot));
if constexpr (FunctionSlotType::IsPointerToMemberFunction) {
pSlot = const_cast<void **>(reinterpret_cast<void *const *>(&slot));
} else {
Q_ASSERT_X((type & Qt::UniqueConnection) == 0, "",
"QObject::connect: Unique connection requires the slot to be a pointer to "
"a member function of a QObject subclass.");
}
} else {
using FunctorSlotType = QtPrivate::FunctionPointer<decltype(&Slot::operator())>;
slotObject = new QtPrivate::QCallableObject<Slot, typename FunctorSlotType::Arguments, typename FunctorSlotType::ReturnType>(std::forward<Func>(slot));
}
return QMetaObject::connectImpl(sender, signal, context, pSlot, slotObject, type);
}
inline void swap(QMetaObject::Connection &lhs, QMetaObject::Connection &rhs) noexcept
{
lhs.swap(rhs);

View File

@ -354,6 +354,10 @@ private slots:
void notifySignalsInParentClass();
void connectByMetaMethodToPMF();
void connectByMetaMethodToFunctor();
void connectByMetaMethodToFreeFunction();
signals:
void value6Changed();
void value7Changed(const QString &);
@ -561,6 +565,7 @@ signals:
QString sig1(QString s1);
void sig10(QString s1, QString s2, QString s3, QString s4, QString s5, QString s6, QString s7,
QString s8, QString s9, QString s10);
void sigWithOneUnregisteredParameterType(QString a1, const MyForwardDeclaredType &a2);
protected:
QtTestObject(QVariant) {}
@ -3039,5 +3044,124 @@ void tst_QMetaObject::notifySignalsInParentClass()
obj2.metaObject()->property(obj2.metaObject()->indexOfProperty("value3")).notifySignal();
}
void tst_QMetaObject::connectByMetaMethodToPMF()
{
QtTestObject o;
QObject::disconnect(&o, nullptr, nullptr, nullptr);
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, &QtTestObject::sl0);
QVERIFY(connection);
QVERIFY(o.slotResult.isEmpty());
emit o.sig0();
QCOMPARE(o.slotResult, u"sl0"_s);
QVERIFY(QObject::disconnect(connection));
o.slotResult = QString();
emit o.sig0();
QVERIFY(o.slotResult.isEmpty());
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
connection = QMetaObject::connect(&o, sig1Signal, &o, &QtTestObject::sl1);
QVERIFY(connection);
QCOMPARE(emit o.sig1(u"toto"_s), u"yessir"_s);
QCOMPARE(o.slotResult, u"sl1:toto"_s);
QVERIFY(QObject::disconnect(connection));
o.slotResult = QString();
QCOMPARE(emit o.sig1(u"tata"_s), QString());
QVERIFY(o.slotResult.isEmpty());
connection = QMetaObject::connect(&o, sig0Signal, &o, qOverload<>(&QtTestObject::overloadedSlot));
QVERIFY(connection);
emit o.sig0();
QCOMPARE(o.slotResult, u"overloadedSlot"_s);
o.slotResult = QString();
connection = QMetaObject::connect(&o, sig1Signal, &o, &QtTestObject::sl1, Qt::QueuedConnection);
QVERIFY(connection);
QCOMPARE(emit o.sig1(u"titi"_s), QString());
QVERIFY(o.slotResult.isEmpty());
qApp->processEvents(QEventLoop::AllEvents);
QCOMPARE(o.slotResult, u"sl1:titi"_s);
QVERIFY(QObject::disconnect(connection));
QMetaMethod sigWithOneUnregisteredParameterTypeSignal = QMetaMethod::fromSignal(&QtTestObject::sigWithOneUnregisteredParameterType);
connection = QMetaObject::connect(&o, sigWithOneUnregisteredParameterTypeSignal, &o, &QtTestObject::slotWithOneUnregisteredParameterType);
QVERIFY(connection);
emit o.sigWithOneUnregisteredParameterType(u"tutu"_s, getForwardDeclaredType());
QCOMPARE(o.slotResult, u"slotWithUnregisteredReturnType-tutu"_s);
QVERIFY(QObject::disconnect(connection));
}
void tst_QMetaObject::connectByMetaMethodToFunctor()
{
QtTestObject o;
QObject::disconnect(&o, nullptr, nullptr, nullptr);
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
int called = 0;
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, [&called]() { ++called; });
QVERIFY(connection);
emit o.sig0();
QCOMPARE(called, 1);
QVERIFY(QObject::disconnect(connection));
emit o.sig0();
QCOMPARE(called, 1);
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
QString receivedValue;
connection = QMetaObject::connect(&o, sig1Signal, &o, [&receivedValue](QString s1) { receivedValue = s1; return s1; });
QVERIFY(connection);
QString returnValue = emit o.sig1(u"foo"_s);
QCOMPARE(receivedValue, u"foo"_s);
QCOMPARE(returnValue, u"foo"_s);
QVERIFY(QObject::disconnect(connection));
receivedValue = QString();
returnValue = emit o.sig1(u"bar"_s);
QVERIFY(receivedValue.isEmpty());
QVERIFY(returnValue.isEmpty());
connection = QMetaObject::connect(&o, sig1Signal, &o, [&receivedValue](QString s1) { receivedValue = s1; return s1; }, Qt::QueuedConnection);
QVERIFY(connection);
returnValue = emit o.sig1(u"baz"_s);
QVERIFY(receivedValue.isEmpty());
QVERIFY(returnValue.isEmpty());
qApp->processEvents(QEventLoop::AllEvents);
QCOMPARE(receivedValue, u"baz"_s);
}
QString freeFunction(const QString &s)
{
return s + s;
}
void tst_QMetaObject::connectByMetaMethodToFreeFunction()
{
QtTestObject o;
QObject::disconnect(&o, nullptr, nullptr, nullptr);
QMetaMethod sig0Signal = QMetaMethod::fromSignal(&QtTestObject::sig0);
QMetaObject::Connection connection = QMetaObject::connect(&o, sig0Signal, &o, &QtTestObject::staticFunction0);
QVERIFY(connection);
emit o.sig0();
QCOMPARE(o.staticResult, u"staticFunction0"_s);
QVERIFY(QObject::disconnect(connection));
QMetaMethod sig1Signal = QMetaMethod::fromSignal(&QtTestObject::sig1);
connection = QMetaObject::connect(&o, sig1Signal, &o, freeFunction);
QVERIFY(connection);
QCOMPARE(emit o.sig1(u"foo"_s), u"foofoo"_s);
}
QTEST_MAIN(tst_QMetaObject)
#include "tst_qmetaobject.moc"