在Qt 5中连接过载信号和插槽

Connecting overloaded signals and slots in Qt 5

本文关键字:信号 插槽 连接 Qt      更新时间:2023-10-16

我很难掌握Qt 5中的新信号/槽语法(使用指针指向成员函数),如新信号槽语法所述。我试着改变这个:

QObject::connect(spinBox, SIGNAL(valueChanged(int)),
                 slider, SLOT(setValue(int));

:

QObject::connect(spinBox, &QSpinBox::valueChanged,
                 slider, &QSlider::setValue);

,但我得到一个错误,当我试图编译它:

错误:没有匹配的函数调用QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

我试过在Linux上使用clang和gcc,它们都是-std=c++11

我做错了什么,我该如何修复它?

这里的问题是,有两个信号与该名称:QSpinBox::valueChanged(int)QSpinBox::valueChanged(QString)。从Qt 5.7开始,提供了帮助函数来选择所需的重载,因此您可以编写

connect(spinbox, qOverload<int>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

对于Qt 5.6和更早的版本,你需要告诉Qt你想选择哪一个,通过将它转换为正确的类型:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
        slider, &QSlider::setValue);

我知道,它很丑。但这是没有办法的。今天的课程是:不要让你的信号和插槽过载!


附录:真正让人讨厌的是

  1. 类名重复两次
  2. 必须指定返回值,即使它通常是void(信号)。

所以我发现自己有时会使用c++ 11代码片段:

template<typename... Args> struct SELECT { 
    template<typename C, typename R> 
    static constexpr auto OVERLOAD_OF( R (C::*pmf)(Args...) ) -> decltype(pmf) { 
        return pmf;
    } 
};

用法:

connect(spinbox, SELECT<int>::OVERLOAD_OF(&QSpinBox::valueChanged), ...)

我个人觉得它没什么用。我希望当创建者(或您的IDE)在自动完成采取PMF的操作时自动插入正确的转换时,这个问题会自行消失。但与此同时……

注意:基于pmf的连接语法不需要c++ 11!

附录2:在Qt 5.7中添加了帮助函数来缓解这个问题,模仿我上面的解决方法。主要的帮助器是qOverload(也有qConstOverloadqNonConstOverload)。

用法示例(来自文档):

struct Foo {
    void overloadedFunction();
    void overloadedFunction(int, QString);
};
// requires C++14
qOverload<>(&Foo:overloadedFunction)
qOverload<int, QString>(&Foo:overloadedFunction)
// same, with C++11
QOverload<>::of(&Foo:overloadedFunction)
QOverload<int, QString>::of(&Foo:overloadedFunction)

附录3:如果您查看任何重载信号的文档,现在重载问题的解决方案都在文档本身中明确说明。例如,https://doc.qt.io/qt-5/qspinbox.html#valueChanged-1表示

注意:信号valueChanged在这个类中是重载的。为了通过使用函数指针语法连接到这个信号,Qt提供了一个方便的助手来获取函数指针,如下例所示:

   connect(spinBox, QOverload<const QString &>::of(&QSpinBox::valueChanged),
[=](const QString &text){ /* ... */ });

错误信息为:

错误:没有匹配的函数调用QObject::connect(QSpinBox*&, <unresolved overloaded function type>, QSlider*&, void (QAbstractSlider::*)(int))

其中重要的部分是提到了"未解析的重载函数类型"。编译器不知道你指的是QSpinBox::valueChanged(int)还是QSpinBox::valueChanged(QString)

有几种方法可以解决重载:

  • connect()提供合适的模板参数

    QObject::connect<void(QSpinBox::*)(int)>(spinBox, &QSpinBox::valueChanged,
                                             slider,  &QSlider::setValue);
    

    强制connect()&QSpinBox::valueChanged解析为接受int的重载。

    如果您对slot参数有无法解析的重载,那么您需要向connect()提供第二个模板参数。不幸的是,没有要求推断第一个的语法,因此需要同时提供这两个。这时第二种方法就可以帮上忙了:

  • 使用正确类型的临时变量

    void(QSpinBox::*signal)(int) = &QSpinBox::valueChanged;
    QObject::connect(spinBox, signal,
                     slider,  &QSlider::setValue);
    

    signal的赋值将选择所需的重载,现在它可以成功地替换到模板中。这与'slot'参数同样有效,并且我发现在这种情况下它不那么麻烦。

  • 使用转换

    我们可以在这里避免static_cast,因为它只是强制而不是删除语言的保护。我使用如下:

    // Also useful for making the second and
    // third arguments of ?: operator agree.
    template<typename T, typename U> T&& coerce(U&& u) { return u; }
    

    这允许我们写

    QObject::connect(spinBox, coerce<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged),
                     slider, &QSlider::setValue);
    

实际上,你可以用lambda和这个来包装你的插槽:

connect(spinbox, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
    slider, &QSlider::setValue);

将看起来更好。:

上面的解决方案是有效的,但我用一种稍微不同的方式解决了这个问题,使用了一个宏,所以以防万一,这里是:

#define CONNECTCAST(OBJECT,TYPE,FUNC) static_cast<void(OBJECT::*)(TYPE)>(&OBJECT::FUNC)

添加到你的代码中。

然后,你的例子:

QObject::connect(spinBox, &QSpinBox::valueChanged,
             slider, &QSlider::setValue);

就变成:

QObject::connect(spinBox, CONNECTCAST(QSpinBox, double, valueChanged),
             slider, &QSlider::setValue);