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

我在理解 Qt 5中新的信号/槽语法(使用成员函数指针)方面遇到了麻烦,如 新的信号槽语法所述。我试图改变这一点:

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

回到这里:

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

但是当我尝试编译它的时候,我得到了一个错误:

错误: 调用 < code > QObject: : connect (QSpinBox * & , < 未解析的重载函数类型 > ,QSlider * & ,void (QAbstractSlider: : *)(int)

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

我做错了什么,我该如何弥补?

71511 次浏览

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

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

我个人认为它并不真正有用。当 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

注意: SignalvalueChanged 在此类中被重载。要使用函数指针语法连接到这个信号,Qt 提供了一个方便的帮助来获取函数指针,如下例所示:

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

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

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

会更好看

错误消息是:

错误: 对 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的重载。

    如果存在槽参数的未解析重载,则需要向 connect()提供第二个模板参数。不幸的是,没有语法要求推断出第一个,因此需要同时提供这两种语法。这时,第二种方法可能会有所帮助:

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

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

    signal的赋值将选择所需的重载,现在它可以成功地替换到模板中。这种方法同样适用于“插槽”参数,在这种情况下,我发现它不那么麻烦。

  • 使用转换

    在这里我们可以避免使用 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);
    

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

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