Spec-Zone.ru › Qt 5.11

Различия между соединениями на основе строк и на основе функторов

Начиная с Qt 5.0, Qt предлагает два разных способа записи соединений «сигнал-слот» в C++: синтаксис соединения на основе строк и синтаксис соединения на основе функторов. У каждого синтаксиса есть свои плюсы и минусы. Таблица ниже обобщает их различия.

На основе строк На основе функторов
Проверка типов выполняется в... Во время выполнения На этапе компиляции
Можно выполнять неявные преобразования типов Да
Можно соединять сигналы с лямбда-выражениями Да
Можно соединять сигналы со слотами, имеющими больше аргументов, чем сигнал (используя параметры по умолчанию) Да
Можно соединять функции C++ с функциями QML Да

Следующие разделы подробно объясняют эти различия и демонстрируют, как использовать функции, уникальные для каждого синтаксиса соединения.

Проверка типов и неявные преобразования типов

Соединения на основе строк проверяют типы, сравнивая строки во время выполнения. Этот подход имеет три ограничения:

  1. Ошибки соединения могут быть обнаружены только после запуска программы.
  2. Неявные преобразования не могут выполняться между сигналами и слотами.
  3. Псевдонимы типов и пространства имен не могут быть разрешены.

Ограничения 2 и 3 существуют, потому что компаратор строк не имеет доступа к информации о типах C++, поэтому он полагается на точное соответствие строк.

В отличие от этого, соединения на основе функторов проверяются компилятором. Компилятор обнаруживает ошибки на этапе компиляции, позволяет выполнять неявные преобразования между совместимыми типами и распознает различные имена одного и того же типа.

Например, только синтаксис на основе функторов может использоваться для соединения сигнала, содержащего int, со слотом, принимающим double. 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" уже отображается. Эта проблема не относится к соединениям на основе функторов, так как типы аргументов не являются частью соединения.

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

    // ...

Соединение с лямбда-выражениями

Синтаксис соединения на основе функторов позволяет соединять сигналы с лямбда-выражениями C++11, которые представляют собой эффективные встроенные слоты. Эта функция недоступна с помощью синтаксиса соединения на основе строк.

В следующем примере класс 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() имеют несовместимые параметры. В противоположность этому, реализация на основе строк потребовала бы дополнительного вспомогательного кода.

Примечание: синтаксис соединения на основе функторов принимает указатели на все функции, включая отдельные функции и обычные методы-члены. Однако для повышения читабельности сигналы следует соединять только со слотами, лямбда-выражениями и другими сигналами.

Соединение объектов C++ с объектами QML

Синтаксис на основе строк позволяет соединять объекты C++ с объектами QML, но синтаксис на основе функторов этого не делает. Это связано с тем, что типы 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++:

class CppGui : public QWidget {
    Q_OBJECT

    QPushButton *button;

signals:
    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("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)));

Примечание: все функции JavaScript в QML принимают параметры типа var, который соответствует типу QVariant в C++.

При нажатии на QPushButton в консоль выводится 'QML received: "Hello from C++!"'. Аналогично, при нажатии на прямоугольник в консоль выводится 'C++ received: "Hello from QML!"'.

См. Взаимодействие с объектами QML из C++ для получения дополнительной информации о способах взаимодействия объектов C++ с объектами QML.

Использование параметров по умолчанию в слотах для подключения к сигналам с меньшим числом параметров

Обычно соединение может быть установлено только в том случае, если у слота такое же количество аргументов, как у сигнала (или меньше), и если все типы аргументов совместимы.

Синтаксис соединения на основе строк предоставляет обходной путь для этого правила: если слот имеет параметры по умолчанию, эти параметры можно опустить из сигнала. Когда сигнал излучается с меньшим количеством аргументов, чем слот, Qt выполняет слот, используя значения параметров по умолчанию.

Соединения на основе функторов не поддерживают эту функцию.

Предположим, есть класс под названием DemoWidget со слотом printNumber(), имеющим параметр по умолчанию:

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

Используя соединение на основе строк, DemoWidget::printNumber() можно подключить к QApplication::aboutToQuit(), даже если последний не имеет аргументов. Соединение на основе функторов вызовет ошибку компиляции:

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

    // OK: printNumber() will be called with a default value of 42
    connect(qApp, SIGNAL(aboutToQuit()),
            this, SLOT(printNumber()));

    // ERROR: Compiler requires compatible arguments
    connect(qApp, &QCoreApplication::aboutToQuit,
            this, &DemoWidget::printNumber);
}

Для обхода этого ограничения с помощью синтаксиса на основе функторов подключите сигнал к лямбда-функции, которая вызывает слот. См. раздел выше, Соединение с лямбда-выражениями.

Выбор перегруженных сигналов и слотов

В синтаксисе на основе строк типы параметров указываются явно. В результате, требуемый экземпляр перегруженного сигнала или слота является однозначным.

В отличие от этого, в синтаксисе на основе функторов необходимо привести перегруженный сигнал или слот к типу, чтобы сообщить компилятору, какой экземпляр использовать.

Например, у QLCDNumber есть три версии слота display():

  1. QLCDNumber::display(int)
  2. QLCDNumber::display(double)
  3. 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, first alternative
    connect(slider, &QSlider::valueChanged,
            lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));

    // Functor-based syntax, second alternative
    void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;
    connect(slider, &QSlider::valueChanged,
            lcd, mySlot);

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

    // Functor-based syntax, fourth alternative (requires C++14)
    connect(slider, &QSlider::valueChanged,
            lcd, qOverload<int>(&QLCDNumber::display));

См. также qOverload().

© The Qt Company Ltd
Licensed under the GNU Free Documentation License, Version 1.3.
https://doc.qt.io/archives/qt-5.11/signalsandslots-syntaxes.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API