Spec-Zone.ru › Qt 6.0

Различия между подключениями на основе строк и функциональных подключений

Начиная с 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++!"'. Аналогично, при нажатии на Rectangle в консоль выводится '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
    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.0/signalsandslots-syntaxes.html

Spec-Zone.ru

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