2 solutions, I'll be looking for a 3rd.
Simple class used for the examples:
#include <QObject>
#include <QDebug>
class MyClass : public QObject
{
Q_OBJECT
public:
MyClass() : QObject() { }
void fireSignals()
{
qDebug() << "signal1 :" ;
emit signal1();
qDebug() << "signal2 :" ;
emit signal2();
qDebug() << "signal3 :" ;
emit signal3(3);
}
public slots:
void slot1() { qDebug() << "slot1"; }
void slot2() { qDebug() << "slot2"; }
void slot3() { qDebug() << "slot3"; }
void slot4(int i) { qDebug() << "slot4" << i; }
signals:
void signal1();
void signal2();
void signal3(int);
};
Qt4/C++98:
I do not have Qt4, please tell me if this solution works.
Tested with Qt 5.5, mingw 4.9.2, -std=c++98
It relies on transforming a signal or slot name into its index, and then into the associated QMetaMethod.
Note: it is possible to store QMetaMethod
objects instead. This method is safer, as the check for the slot existence happens earlier on, but having a MyClass
instance is needed before the creation of the QMap
, while storing slot names is more flexible.
// Get the index of a method, using obj's metaobject
QMetaMethod fetchIndexOfMethod(QObject* obj, const char* name)
{
const QMetaObject* meta_object = obj->metaObject();
QByteArray normalized_name = QMetaObject::normalizedSignature(name);
int index = meta_object->indexOfMethod(normalized_name.constData());
Q_ASSERT(index != -1);
return meta_object->method(index);
}
// A QObject::connect wrapper
QMetaObject::Connection dynamicConnection(QObject* source,
const char* signal_name,
QObject* dest,
const char* slot_name)
{
return QObject::connect(source, fetchIndexOfMethod(source, signal_name),
dest, fetchIndexOfMethod(dest, slot_name));
}
void first_way()
{
qDebug() << "\nFirst way:";
QMap<QString, const char*> my_slots;
my_slots["id_slot1"] = "slot1()";
my_slots["id_slot2"] = "slot2()";
my_slots["id_slot3"] = "slot3()";
my_slots["id_slot4"] = "slot4(int)"; // slots with different signatures in the same container
MyClass object;
dynamicConnection(&object, "signal1()", &object, my_slots.value("id_slot1"));
dynamicConnection(&object, "signal1()", &object, my_slots.value("id_slot2"));
dynamicConnection(&object, "signal2()", &object, my_slots.value("id_slot3"));
dynamicConnection(&object, "signal3(int)", &object, my_slots.value("id_slot4"));
object.fireSignals();
}
Qt5/C++14:
Tested with Qt 5.5, mingw 4.9.2, -std=c++14
This one is using pointer to members function.
It is type-safe, thus you can't have slots with different signatures in the same container.
template <typename T, typename R, typename ...Args>
struct PointerToMemberHelper
{
using type = R (T::*)(Args...);
};
void second_way()
{
qDebug() << "\nSecond way:";
using MPTR_void = PointerToMemberHelper<MyClass, void>::type;
using MPTR_int = PointerToMemberHelper<MyClass, void, int>::type;
QMap<QString, MPTR_void> my_slots({{"id_slot1", MyClass::slot1},
{"id_slot2", MyClass::slot2},
{"id_slot3", MyClass::slot3}});
MyClass object;
QObject::connect(&object, MyClass::signal1, &object, my_slots.value("id_slot1"));
QObject::connect(&object, MyClass::signal1, &object, my_slots.value("id_slot2"));
QObject::connect(&object, MyClass::signal2, &object, my_slots.value("id_slot3"));
MPTR_int my_int_slot = MyClass::slot4; // or auto my_int_slot = ...
QObject::connect(&object, MyClass::signal3, &object, my_int_slot);
object.fireSignals();
}
Currently trying to get a 3rd solution to work, based on lambdas and QMetaObject::invokeMethod
- but it won't do more than the 2nd solution.