Unterschiede zwischen String-basierten und Functor-basierten Verbindungen

Qt bietet zwei verschiedene Möglichkeiten, Signal-Slot-Verbindungen in C++ zu schreiben: Die stringbasierte Verbindungssyntax und die funktorbasierte Verbindungssyntax. Beide Syntaxen haben ihre Vor- und Nachteile. Die folgende Tabelle fasst die Unterschiede zusammen.

String-basiertFunktorbasiert
Die Typüberprüfung erfolgt zur...LaufzeitKompilierzeit
Kann implizite Typkonvertierungen durchführenY
Kann Signale mit Lambda-Ausdrücken verbindenY
Kann Signale mit Slots verbinden, die mehr Argumente haben als das Signal (unter Verwendung von Standardparametern)Y
Kann C++-Funktionen mit QML-Funktionen verbindenY

In den folgenden Abschnitten werden diese Unterschiede im Detail erläutert und die Verwendung der einzigartigen Funktionen der einzelnen Verbindungssyntaxen demonstriert.

Typprüfung und implizite Typkonvertierung

Bei String-basierten Verbindungen erfolgt die Typprüfung durch den Vergleich von Strings während der Laufzeit. Bei diesem Ansatz gibt es drei Einschränkungen:

  1. Verbindungsfehler können erst erkannt werden, wenn das Programm bereits läuft.
  2. Es können keine impliziten Konvertierungen zwischen Signalen und Slots durchgeführt werden.
  3. Typedefs und Namespaces können nicht aufgelöst werden.

Die Einschränkungen 2 und 3 bestehen, weil der Stringkomparator keinen Zugriff auf C++-Typinformationen hat und daher auf eine exakte Stringübereinstimmung angewiesen ist.

Im Gegensatz dazu werden funktorbasierte Verbindungen durch den Compiler überprüft. Der Compiler fängt Fehler zur Kompilierzeit ab, ermöglicht implizite Konvertierungen zwischen kompatiblen Typen und erkennt unterschiedliche Namen desselben Typs.

Zum Beispiel kann nur die funktorbasierte Syntax verwendet werden, um ein Signal, das einen int trägt, mit einem Slot zu verbinden, der einen double akzeptiert. Ein QSlider hält einen int Wert, während ein QDoubleSpinBox einen double Wert hält. Der folgende Ausschnitt zeigt, wie man sie synchron hält:

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

Das folgende Beispiel veranschaulicht das Fehlen der Namensauflösung. QAudioInput::stateChanged() wird mit einem Argument vom Typ "QAudio::State" deklariert. Daher müssen String-basierte Verbindungen auch "QAudio::State" angeben, selbst wenn "State" bereits sichtbar ist. Dieses Problem gilt nicht für funktorbasierte Verbindungen, da Argumenttypen nicht Teil der Verbindung sind.

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

    // ...

Verbindungen zu Lambda-Ausdrücken herstellen

Die funktorbasierte Verbindungssyntax kann Signale mit C++11-Lambda-Ausdrücken verbinden, die effektiv Inline-Slots sind. Diese Funktion ist mit der String-basierten Syntax nicht verfügbar.

Im folgenden Beispiel gibt die Klasse TextSender ein Signal textCompleted() aus, das einen Parameter QString enthält. Hier ist die Klassendeklaration:

class TextSender : public QWidget {
    Q_OBJECT

    QLineEdit *lineEdit;
    QPushButton *button;

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

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

Hier ist die Verbindung, die TextSender::textCompleted() ausgibt, wenn der Benutzer auf die Schaltfläche klickt:

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

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

    // ...
}

In diesem Beispiel hat die Lambda-Funktion die Verbindung vereinfacht, obwohl QPushButton::clicked() und TextSender::textCompleted() inkompatible Parameter haben. Im Gegensatz dazu würde eine String-basierte Implementierung zusätzlichen Boilerplate-Code erfordern.

Hinweis: Die funktorbasierte Verbindungssyntax akzeptiert Zeiger auf alle Funktionen, einschließlich eigenständiger Funktionen und regulärer Mitgliedsfunktionen. Aus Gründen der Lesbarkeit sollten Signale jedoch nur mit Slots, Lambda-Ausdrücken und anderen Signalen verbunden werden.

Verbinden von C++-Objekten mit QML-Objekten

Die stringbasierte Syntax kann C++-Objekte mit QML-Objekten verbinden, die funktorbasierte Syntax jedoch nicht. Der Grund dafür ist, dass QML-Typen zur Laufzeit aufgelöst werden und somit dem C++-Compiler nicht zur Verfügung stehen.

Im folgenden Beispiel bewirkt ein Klick auf das QML-Objekt, dass das C++-Objekt eine Nachricht ausgibt und umgekehrt. Hier ist der QML-Typ (in 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!")
    }
}

Hier ist die C++-Klasse:

class CppGui : public QWidget { Q_OBJECT    QPushButton *Button;Signale: void cppSignal(const QVariant& sentMsg) const;public slots: void cppSlot(const QString& receivedMsg) const {        qDebug() << "C++ received:" << receivedMsg;
    }public: CppGui(QWidget *parent = nullptr) : QWidget(parent) { button = new QPushButton("Click Me!", this); connect(button, &QPushButton::clicked, [=] { emit cppSignal("Hallo von C++!"); }); } };

Hier ist der Code, der die Signal-Slot-Verbindungen herstellt:

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

Hinweis: Alle JavaScript-Funktionen in QML nehmen Parameter des Typs var entgegen, der dem Typ QVariant in C++ entspricht, es sei denn, sie verwenden Typ-Annotationen. Siehe Aufrufen von QML-Methoden für weitere Details.

Wenn QPushButton angeklickt wird, gibt die Konsole aus : 'QML empfangen: "Hallo von C++!"'. Wenn das Rechteck angeklickt wird, gibt die Konsole aus : 'C++ hat empfangen: "Hallo von QML!"'.

Siehe Interaktion mit QML-Objekten aus C++ für weitere Möglichkeiten, C++-Objekte mit QML-Objekten interagieren zu lassen.

Verwendung von Standardparametern in Slots zur Verbindung mit Signalen mit weniger Parametern

Normalerweise kann eine Verbindung nur hergestellt werden, wenn der Slot die gleiche Anzahl von Argumenten hat wie das Signal (oder weniger) und wenn alle Argumenttypen kompatibel sind.

Die String-basierte Verbindungssyntax bietet eine Umgehung für diese Regel: Wenn der Slot Standardparameter hat, können diese Parameter im Signal weggelassen werden. Wenn das Signal mit weniger Argumenten als der Slot ausgegeben wird, führt Qt den Slot unter Verwendung der Standardparameterwerte aus.

Functor-basierte Verbindungen unterstützen diese Funktion nicht.

Angenommen, es gibt eine Klasse namens DemoWidget mit einem Slot printNumber(), der ein Standardargument hat:

public slots: void printNumber(int number = 42) {        qDebug() << "Lucky number" << number;
    }

Mit einer String-basierten Verbindung kann DemoWidget::printNumber() mit QApplication::aboutToQuit() verbunden werden, auch wenn letztere keine Argumente hat. Die funktorbasierte Verbindung führt zu einem Kompilierfehler:

DemoWidget::DemoWidget(QWidget *parent) : QWidget(parent) { // OK: printNumber() wird mit einem Standardwert von 42 aufgerufen    connect(qApp, SIGNAL(aboutToQuit()),
           this, SLOT(printNumber())); // ERROR: Compiler erfordert kompatible Argumente    connect(qApp, &QCoreApplication::aboutToQuit,
           this, &DemoWidget::printNumber); }

Um diese Einschränkung mit der funktorbasierten Syntax zu umgehen, verbinden Sie das Signal mit einer Lambda-Funktion, die den Slot aufruft. Siehe den Abschnitt oben, Verbindungen zu Lambda-Ausdrücken herstellen.

Auswählen von überladenen Signalen und Slots

Bei der String-basierten Syntax werden die Parametertypen explizit angegeben. Dadurch ist die gewünschte Instanz eines überladenen Signals oder Slots eindeutig.

Im Gegensatz dazu muss bei der funktorbasierten Syntax ein überladenes Signal oder ein Slot gecastet werden, um dem Compiler mitzuteilen, welche Instanz er verwenden soll.

Zum Beispiel hat QLCDNumber drei Versionen des display() Slots:

  1. QLCDNumber::display(int)
  2. QLCDNumber::display(double)
  3. QLCDNumber::display(QString)

Um die Version int mit QSlider::valueChanged() zu verbinden, lauten die beiden Syntaxen:

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

Siehe auch qOverload().

© 2025 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.