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


133

我难以掌握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。我确实尝试添加该包含,但仍然无法编译。
dtruby

另外,我绝对要与Qt 5链接,我使用的是Qt Creator,我正在测试的两个套件都将Qt 5.0.1列为其Qt版本。
dtruby

Answers:


243

这里的问题是,有两个名称相同的信号: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), ...)

我个人认为它不是真正有用。我希望当自动完成拍摄PMF的操作时,Creator(或您的IDE)会自动插入正确的类型转换时,此问题会自行解决。但是与此同时...

注意:基于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){ /* ... */ });

1
是的,这很有意义。我想在这种情况下,如果信号/插槽过载,我会坚持使用旧语法:-)。谢谢!
dtruby

17
我对新语法感到非常兴奋……现在是一阵冰冷的失望。
RushPL

12
对于那些想知道的人(像我一样):“ pmf”代表“指向成员函数的指针”。
Vicky Chijwani,2015年

14
我个人更喜欢static_cast丑陋而不是旧语法,这仅仅是因为新语法可以在编译时检查信号/插槽的存在,而旧语法将在运行时失败。
Vicky Chijwani,2015年

2
不幸的是,不使信号不过载通常不是一种选择-Qt经常使自己的信号过载。(例如QSerialPort
PythonNut

14

错误消息是:

错误:没有匹配的调用函数 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);

8

实际上,您可以仅使用lambda来包装广告位,并执行以下操作:

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

会看起来更好。:\


0

上面的解决方案可以工作,但是我使用宏以稍微不同的方式解决了这个问题,以防万一,这是:

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

2
解决方案“以上”是什么?不要假设答案按照您当前看到的顺序显示给每个人!
Toby Speight

1
您如何将其用于需要多个参数的重载?逗号不会引起问题吗?我认为您确实需要通过parens,即#define CONNECTCAST(class,fun,args) static_cast<void(class::*)args>(&class::fun)- CONNECTCAST(QSpinBox, valueChanged, (double))在这种情况下使用。
Toby Speight

当方括号用于多个参数时,这是一个很好的有用宏,如Toby的注释
–jectamenta
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.