Spec-Zone.ru › Qt 6.1

Отличия между подключениями на основе строк и функторов

Начиная с 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 получило: "Привет из C++!"». Аналогично, при щелчке по прямоугольнику в консоль выводится «C++ получило: "Привет из 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
    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/qt-6.1/signalsandslots-syntaxes.html

Spec-Zone.ru

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