【问题标题】:Connecting overloaded signals and slots in Qt 5在 Qt 5 中连接重载的信号和插槽
【发布时间】:2013-05-23 13:55:52
【问题描述】:

New Signal Slot Syntax 中所述,我无法掌握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

我做错了什么,我该如何解决?

【问题讨论】:

  • 如果你的语法是正确的,那么唯一的解释可能是你没有链接到 Qt5 库,但是例如而是 Qt4。这很容易在“项目”页面上使用 QtCreator 进行验证。
  • 我包含了 QObject 的一些子类(QSpinBox 等),因此应该包含 QObject。我确实尝试添加该包含,但它仍然无法编译。
  • 另外,我肯定链接到 Qt 5,我使用的是 Qt Creator,我正在测试的两个套件都将 Qt 5.0.1 列为其 Qt 版本。

标签: c++ qt qt5


【解决方案1】:

这里的问题是有 两个 具有该名称的信号: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 sn-p:

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), ...)

我个人觉得它不是很有用。我希望当 Creator(或您的 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){ /* ... */ });

【讨论】:

  • 啊,是的,这很有意义。我想对于这种信号/插槽过载的情况,我会坚持使用旧语法:-)。谢谢!
  • 我对新语法感到非常兴奋......现在是冰冷的失望。
  • 对于那些想知道的人(像我一样):“pmf”代表“指向成员函数的指针”。
  • 我个人更喜欢 static_cast 丑陋而不是旧语法,因为 the new syntax enables a compile-time check 存在旧语法在运行时会失败的信号/槽。
  • 不幸的是,不重载信号通常不是一种选择——Qt 经常重载自己的信号。 (例如QSerialPort
【解决方案2】:

错误信息是:

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

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

有几种方法可以解决过载问题:

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

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

    这会强制connect()&amp;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);
    

【讨论】:

    【解决方案3】:

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

    #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);
    

    【讨论】:

    • 解决方案“上面”是什么?不要假设答案是按照您当前看到的顺序呈现给每个人的!
    • 如何将它用于需要多个参数的重载?逗号不会引起问题吗?我认为您确实需要传递括号,即#define CONNECTCAST(class,fun,args) static_cast&lt;void(class::*)args&gt;(&amp;class::fun) - 在这种情况下用作CONNECTCAST(QSpinBox, valueChanged, (double))
    • 当括号用于多个参数时,这是一个非常有用的宏,就像在 Toby 的评论中一样
    【解决方案4】:

    实际上,你可以用 lambda 包裹你的插槽,这样:

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

    会更好看。 :\

    【讨论】:

      猜你喜欢
      • 2012-10-15
      • 1970-01-01
      • 1970-01-01
      • 2015-03-14
      • 2016-11-10
      相关资源
      最近更新 更多