Qt5 alpha увидел свет. В этой статье я опишу одну из фич, над которыми работал — это новый синтаксис сигналов и слотов.
Вот как мы обычно соединяем сигнал и слот:
На самом деле макросы
Не смотря на то, что в целом все работает хорошо, некоторые неудобства все же есть:
Готовящийся Qt5 поддерживает альтернативный синтаксис. Вдобавок к вышеописанному подходу вы сможете использовать вот такой новый способ соединения сигналов и слотов:
Который из них более красивый — дело вкуса. Но привыкнуть к новому варианту очень просто.
Теперь рассмотрим преимущества, которые он дает:
Вы получите ошибку компиляции, если ошибетесь в имени сигнала или слота, или если аргументы слота не будут соответствовать аргументам сигнала. Это сохранит вам время после рефакторинга.
Кроме того использован
Теперь можно не только не опасаясь использовать typedef или пространства имен, но и соединять сигналы со слотами, которые принимают аргументы других типов, если неявное приведение возможно.
В следующем примере мы соединим сигнал, принимающий
Как вы заметили в прошлом примере,
Но теперь мы также можем соединять синал с любой функцией или функтором:
Это может стать очень мощной фичей в сочетании с boost или tr1::bind.
Все описанное прежде работает и со старым C++98. Но если вы используете компилятор поддерживающий C++11, то я настоятельно рекомендую использовать новые языковые возможности. Lambda expressions поддерживаются по крайней мере MSVC 2010, GCC 4.5, clang 3.1. Для последних двух нужно указать
Теперь можно писать вот такой код:
Это позволяет писать асинхронный код очень просто.
Предыдущий синтаксис
Вот как мы обычно соединяем сигнал и слот:
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();
});
}
Это позволяет писать асинхронный код очень просто.