本页内容

基于字符串的连接与基于函数对象的连接之间的区别

Qt 提供了两种在 C++ 中编写信号-槽连接的不同方式:基于字符串的连接语法和基于 functor 的连接语法。这两种语法各有优缺点。下表总结了它们之间的区别。

特性基于字符串基于函数对象
类型检查在运行时进行在编译时进行
可以执行隐式类型转换。否是
可以将信号连接到 lambda 表达式。否是
可以将信号连接到参数数量多于该信号的插槽(使用默认参数)。是否
可以将 C++ 函数连接到 QML 函数。是否

以下各节将详细说明这些差异,并演示如何使用每种连接语法所特有的功能。

类型检查与隐式类型转换

基于字符串的连接通过在运行时比较字符串来进行类型检查。

存在以下限制:

限制原因
只有在程序开始运行后,才能检测到连接错误。不适用
信号和槽之间无法进行隐式转换。字符串比较器无法访问 C++ 类型信息,因此依赖于精确的字符串匹配。
无法解析 typedef 和命名空间。

相比之下,基于函子(functor)的连接会由编译器进行检查。编译器会在编译时捕获错误,支持兼容类型之间的隐式转换,并能识别同一类型的不同名称。

例如,只有基于函子(functor)的语法才能将携带int 的信号连接到接受double 的槽(slot)。QSlider 存储int 值,而QDoubleSpinBox 存储double 值。以下代码片段展示了如何保持它们的同步:

    auto slider = new QSlider(this);
    auto doubleSpinBox = new QDoubleSpinBox(this);

    // OK: The compiler can convert an int into a double
    connect(slider, &QSlider::valueChanged,
            doubleSpinBox, &QDoubleSpinBox::setValue);

    // ERROR: The string table doesn't contain conversion information
    connect(slider, SIGNAL(valueChanged(int)),
            doubleSpinBox, SLOT(setValue(double)));

以下示例说明了名称解析的缺失。 QAudioInput::stateChanged() 的声明中,参数类型为“QAudio::State”。因此,基于字符串的连接也必须指定“QAudio::State”,即使"State" 已经可见。此问题不适用于基于 functor 的连接,因为参数类型不属于连接的一部分。

    auto audioInput = new QAudioInput(QAudioFormat(), this);
    auto widget = new QWidget(this);

    // OK
    connect(audioInput, SIGNAL(stateChanged(QAudio::State)),
            widget, SLOT(show()));

    // ERROR: The strings "State" and "QAudio::State" don't match
    using namespace QAudio;
    connect(audioInput, SIGNAL(stateChanged(State)),
            widget, SLOT(show()));

    // ...

与 lambda 表达式建立连接

基于函数对象的连接语法可以将信号连接到 lambda 表达式,这些表达式实际上是内联槽。基于字符串的语法不支持此功能。

在下面的示例中,TextSender 类会发出一个名为textCompleted() 的信号,该信号携带一个名为QString 的参数。以下是该类的声明:

class TextSender : public QWidget {
    Q_OBJECT

    QLineEdit *lineEdit;
    QPushButton *button;

signals:
    void textCompleted(const QString& text) const;

public:
    TextSender(QWidget *parent = nullptr);
};

以下是当用户点击按钮时触发TextSender::textCompleted() 信号的连接:

TextSender::TextSender(QWidget *parent) : QWidget(parent) {
    lineEdit = new QLineEdit(this);
    button = new QPushButton("Send", this);

    connect(button, &QPushButton::clicked, [=] {
        emit textCompleted(lineEdit->text());
    });

    // ...
}

在此示例中,尽管QPushButton::clicked() 和TextSender::textCompleted() 的参数不兼容,但 lambda 函数使连接过程变得简单。相比之下,基于字符串的实现则需要额外的冗余代码。

注意: 基于functor 的连接语法 接受所有函数的指针,包括独立函数和常规成员函数。但是,为了提高可读性,信号应仅连接到槽、lambda 表达式和其他信号。

将 C++ 对象与 QML 对象连接

基于字符串的语法可以将 C++ 对象连接到 QML 对象,但基于函子(functor)的语法则无法做到这一点。这是因为 QML 类型在运行时才被解析,因此 C++ 编译器无法访问它们。

在下面的示例中,点击 QML 对象会触发 C++ 对象打印一条消息,反之亦然。以下是 QML 类型(位于QmlGui.qml ):

Rectangle {
    width: 100; height: 100

    signal qmlSignal(string sentMsg)
    function qmlSlot(receivedMsg) {
        console.log("QML received: " + receivedMsg)
    }

    MouseArea {
        anchors.fill: parent
        onClicked: qmlSignal("Hello from QML!")
    }
}

以下是 C++ 类:

classCppGui :publicQWidget {
    Q_OBJECT

    QPushButton*button;

signals:
    voidcppSignal(constQVariant&sentMsg)const;

public slots:
    voidcppSlot(constQString&receivedMsg)const{
        qDebug() << "C++ received:" << receivedMsg;
    }

public:
    CppGui(QWidget*parent =nullptr) : QWidget(parent) {
        button= newQPushButton("Click Me!", this);
        connect(button, &QPushButton::clicked, [=]{
            emitcppSignal("Hello from C++!");
        });
    }
};

以下是建立信号-槽连接的代码:

    auto cppObj = new CppGui(this);
    auto quickWidget = new QQuickWidget(QUrl("QmlGui.qml"), this);
    auto qmlObj = quickWidget->rootObject();

    // Connect QML signal to C++ slot
    connect(qmlObj, SIGNAL(qmlSignal(QString)),
            cppObj, SLOT(cppSlot(QString)));

    // Connect C++ signal to QML slot
    connect(cppObj, SIGNAL(cppSignal(QVariant)),
            qmlObj, SLOT(qmlSlot(QVariant)));

注意: 除非使用了类型注释,否则 QML 中的所有 JavaScript 函数都接受类型为 `var ` 的参数,该类型映射到 C++ 中的 `QVariant ` 类型。更多详细信息请参阅《调用 QML 方法》。

当点击QPushButton 时,控制台会输出“QML收到:'Hello from C++!'”。同样,当点击Rectangle时,控制台会输出“C++收到:'Hello from QML!'”。

有关让 C++对象与 QML 对象交互的其他方法,请参阅《从 C++ 与QML对象交互》。

在槽中使用默认参数,以更少的参数连接信号

通常,只有当槽的参数个数与信号相同(或少于信号)且所有参数类型兼容时,才能建立连接。

基于字符串的连接语法为这条规则提供了一个变通方案:如果槽具有默认参数,则可以在信号中省略这些参数。当信号发出的参数数量少于槽所需的参数数量时,Qt 会使用默认参数值来执行该槽。

基于函子的连接不支持此功能。

假设有一个名为DemoWidget 的类,其中包含一个名为printNumber() 的槽,该槽有一个默认参数:

public slots:
    voidprintNumber(intnumber= 42) {
        qDebug() << "Lucky number" << number;
    }

使用基于字符串的连接,DemoWidget::printNumber() 可以连接到QApplication::aboutToQuit(),尽管后者没有参数。而基于 functor 的连接会引发编译时错误:

DemoWidget::DemoWidget(QWidget*parent) : QWidget(parent) {

    // 正确:printNumber() 将被调用,并传入默认值 42
    connect(qApp, SIGNAL(aboutToQuit()),
           this,SLOT(printNumber()));

    // 错误:编译器要求参数兼容
    connect(qApp, &QCoreApplication::aboutToQuit,
           this, &DemoWidget::printNumber);
}

要绕过基于 functor 的语法中的这一限制,请将信号连接到一个调用该槽的 lambda 函数。请参阅上文“连接到 lambda 表达式”一节。

选择重载的信号和槽

在基于字符串的语法中,参数类型会被显式指定。因此,重载信号或插槽的预期实例是明确无误的。

相比之下,在基于函子(functor)的语法中,您必须显式指定要连接的重载信号或槽的哪个版本。

例如,QLCDNumber 包含三个版本的 `display() ` 槽:

  • QLCDNumber::display(int)
  • QLCDNumber::display(double)
  • QLCDNumber::display(QString)

若要将int 版本连接到QSlider::valueChanged(),有以下两种语法:

    auto slider = new QSlider(this);
    auto lcd = new QLCDNumber(this);

    // String-based syntax
    connect(slider, SIGNAL(valueChanged(int)),
            lcd, SLOT(display(int)));

    // Functor-based syntax
    connect(slider, &QSlider::valueChanged,
            lcd, qOverload<int>(&QLCDNumber::display));

有关使用qOverload()、static_cast 和 lambda 表达式连接重载信号和槽的全面示例,请参阅《连接重载信号和槽》。

另请参阅 qOverload()。

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.