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