如何将Qt信号/槽名存储到数据结构中

How to store Qt signal/slot names to a data structure?

本文关键字:存储 数据结构 Qt 信号      更新时间:2023-10-16

我想将一个信号或插槽的名称存储到一个容器中,以便我可以检索它并与它建立连接。

一种方法是破解SIGNAL/SLOT宏并将信号/SLOT的名称存储为字符串,然后使用旧的连接语法创建如下连接
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.
}

然而,这对我来说似乎有点太粗俗了。

我知道有另一种语法连接,也有一个对象类型的信号/插槽,这意味着一个不同的和(潜在的)不太黑客的解决方案。(注意,这不是Qt5中新的信号/槽语法)

但问题是,QMetaMethod没有一个文档化的构造函数,它似乎不是可复制构造的。

还有一个问题,当信号/插槽的名称是动态的时,如何进行连接?

对于我的编码环境是Qt4和gcc 4.3.3与gnu++98,所以需要Qt5和c++ 11的解决方案虽然受欢迎,但对我来说不太有用。

两个解决方案,我将寻找第三个。

用于示例的简单类:

#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:

我没有Qt4,请告诉我这个解决方案是否有效。

已测试Qt 5.5, mingw 4.9.2, -std=c++98

它依赖于将信号或槽名转换为其索引,然后转换为相关的qmetamemethod。

注意:也可以存储QMetaMethod对象。这种方法更安全,因为对插槽是否存在的检查更早发生,但是在创建QMap之前需要有一个MyClass实例,而存储插槽名称则更灵活。

// 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:

已测试Qt 5.5, mingw 4.9.2, -std=c++14

使用指针指向成员函数。它是类型安全的,因此在同一个容器中不能有不同签名的槽。

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();
}

目前正在尝试获得第三个解决方案的工作,基于lambdas和QMetaObject::invokeMethod -但它不会比第二个解决方案做得更多。