0

I want to store the name of a signal or slot to a container so that I can retrieve it and make connections to it.

One way to do it is to hack the SIGNAL/SLOT macro and store the signal/slot's name as strings, and then use the the old connect syntax to make the connection like this

void connect(QObject *dst, QString slot)
{
    connect(this, SIGNAL(foo(void)), dst, slot.toLocal8Bit().data());
    // void foo(void) is a member signal of "this" and is known 
    // at compile time
    // but "dst" and "slot" are not known until runtime.
}

However this seems a little too hacky to me.

I know there is another syntax for connect and there is also an object type for signal/slots, which implies a different and (potentially) less hacky solution. (note that this is not the new signal/slot syntax in Qt5)

But the problem is, QMetaMethod does not have a documented constructor, not does it seems to be copy-constructable.

A further question, how do you make connections when the names of signal/slots are dynamic?

For my coding environment is Qt4 and gcc 4.3.3 with gnu++98, so solutions that require Qt5 and C++11 although are welcome, are less useful to me.

Community
  • 1
  • 1
user3528438
  • 2,737
  • 2
  • 23
  • 42

1 Answers1

2

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

Armaghast
  • 753
  • 1
  • 8
  • 14
  • Your first solution is more complicated than my string based method, but it demonstrates how to obtain the `QMetaMethod` representation of the slot, which is better than string for `QMetaMethod` strongly typed. Also the string based method can not detect error until connection happens, `QMetaMethod` method can detect errors when the slot is looked up. – user3528438 Aug 05 '15 at 23:11
  • I'm not very familiar with template. Can you explain more on your `PointerToMemberHelper`? – user3528438 Aug 05 '15 at 23:13
  • I think I found the answer here http://stackoverflow.com/questions/10747810/what-is-the-difference-between-typedef-and-using-in-c11 . `using` in this case is similar to `typedef`. – user3528438 Aug 05 '15 at 23:24
  • Indeed, making the `QMetaMethod` earlier is safer, although the string-based one allows you to store slots without a `MyClass` instance, and allow some more powerful (and dangerous) bindings, like constructing the slot name `dynamicConnection( [...], QString("slot%1()").arg(an_int))`. I'll edit my answer to reflect that. – Armaghast Aug 06 '15 at 08:47