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

时间:2015-08-05 15:04:35

标签: c++ qt qt4

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

执行此操作的一种方法是hack the SIGNAL/SLOT macro并将信号/广告位的名称存储为字符串,然后使用old connect syntax进行此类连接

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

然而,这对我来说似乎有些太过分了。

我知道有another syntax for connect并且还有an object type for signal/slots,这意味着不同的(可能)不那么狡猾的解决方案。 (请注意,这不是new signal/slot syntax in Qt5

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

另一个问题是,当信号/插槽的名称是动态的时,如何建立连接?

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

1 个答案:

答案 0 :(得分:2)

2个解决方案,我正在寻找第3个。

用于示例的简单类:

#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

它依赖于将信号或插槽名称转换为索引,然后转换为关联的QMetaMethod。

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

// 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获得第三个解决方案 - 但它不会比第二个解决方案做得更多。