Сигналы и слоты в Qt5

в 19:40, , рубрики: c++, qt, Qt Software, qt5, с++11

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

Предыдущий синтаксис

Вот как мы обычно соединяем сигнал и слот:

connect(sender, SIGNAL(valueChanged(QString,QString)),
        receiver, SLOT(updateValue(QString)) );

На самом деле макросы SIGNAL and SLOT преобразуют свои аргументы в строки. Затем QObject::connect() сравнит эти строки с данными интроспекции собранными утилитой moc.

В чем проблема этого синтаксиса?

Не смотря на то, что в целом все работает хорошо, некоторые неудобства все же есть:

  • Невозможность проверки во время компиляции: Все проверки осуществляются во время исполнения, после парсинга строк. А это значит, что если в название сигнала или слота вкрадется опечатка, то программа успешно скомпилируется, но соединение не будет создано. Все что мы увидим — это предупреждение во время исполнения.
  • Так как все операции проходят со строками, имена типов в слотах обязаны буквально совпадать с именами типов в сигналах. Кроме того они должны совпадать в заголовочных файлах и в коде, описывающем соединение. А это означает проблемы при попытке использовать typedef-ы или пространства имен.

Новый синтаксис: использование указателей на функции

Готовящийся Qt5 поддерживает альтернативный синтаксис. Вдобавок к вышеописанному подходу вы сможете использовать вот такой новый способ соединения сигналов и слотов:

connect(sender, &Sender::valueChanged,
        receiver, &Receiver::updateValue );

Который из них более красивый — дело вкуса. Но привыкнуть к новому варианту очень просто.

Теперь рассмотрим преимущества, которые он дает:

Проверка во время компиляции

Вы получите ошибку компиляции, если ошибетесь в имени сигнала или слота, или если аргументы слота не будут соответствовать аргументам сигнала. Это сохранит вам время после рефакторинга.

Кроме того использован static_assert чтобы показывать понятные ошибки в случаях, если аргументы не совпадают или пропущен Q_OBJECT.

Автоматическое приведение типов аргументов

Теперь можно не только не опасаясь использовать typedef или пространства имен, но и соединять сигналы со слотами, которые принимают аргументы других типов, если неявное приведение возможно.

В следующем примере мы соединим сигнал, принимающий QString как параметр, со слотом, который принимает QVariant. Это сработает без проблем, так как QVariant имеет неявный конструктор, принимающий QString.

class Test : public QObject
{ Q_OBJECT
public:
    Test() {
        connect(this, &Test::someSignal, this, &Test::someSlot);
    }
signals:
    void someSignal(const QString &);
public:
    void someSlot(const QVariant &);
};

Соединяем сигнал с любой функцией

Как вы заметили в прошлом примере, someSlot был объявлен просто как публичный метод, без slot. Qt может напрямую вызвать слот и больше не нуждается в интроспекции для этого. (Хотя она все еще нужна для сигналов)

Но теперь мы также можем соединять синал с любой функцией или функтором:

static void someFunction() {
    qDebug() << "pressed";
}
// ... somewhere else
    QObject::connect(button, &QPushButton::clicked, someFunction);

Это может стать очень мощной фичей в сочетании с boost или tr1::bind.

Анонимные функции из C++11

Все описанное прежде работает и со старым C++98. Но если вы используете компилятор поддерживающий C++11, то я настоятельно рекомендую использовать новые языковые возможности. Lambda expressions поддерживаются по крайней мере MSVC 2010, GCC 4.5, clang 3.1. Для последних двух нужно указать -std=c++0x как флаг.

Теперь можно писать вот такой код:

void MyWindow::saveDocumentAs() {
    QFileDialog *dlg = new QFileDialog();
    dlg->open();
    QObject::connect(dlg, &QDialog::finished, [=](int result) {
        if (result) {
            QFile file(dlg->selectedFiles().first());
            // ... save document here ...
        }
        dlg->deleteLater();
    });
}

Это позволяет писать асинхронный код очень просто.

Автор: ocyril

Поделиться

* - обязательные к заполнению поля