Spec-Zone.ru › Qt 5.12

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

Начиная с 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, 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/qt-5.12/signalsandslots-syntaxes.html

Spec-Zone.ru

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