Qt5 alpha saw the light. In this article I will describe one of the features that I worked on - this is a new syntax of signals and slots.
Previous syntax
Here is how we usually connect the signal and the slot:
connect(sender, SIGNAL(valueChanged(QString,QString)), receiver, SLOT(updateValue(QString)) );
In fact, the
SIGNAL
and
SLOT
macros convert their arguments to strings. Then
QObject::connect()
compares these strings with the introspection data collected by the
moc
utility.
')
What is the problem with this syntax?
Despite the fact that, in general, everything works well, there are some inconveniences:
- Impossibility of checking at compile time: All checks are performed at runtime, after parsing strings. And this means that if a typo creeps into the name of a signal or slot, the program will successfully compile, but the connection will not be created. All we will see is a warning during execution.
- Since all operations are performed with strings, the names of the types in the slots must literally coincide with the names of the types in the signals. In addition, they must match in the header files and in the code describing the connection. This means problems when trying to use typedefs or namespaces.
New syntax: use of function pointers
Qt5 in preparation supports alternative syntax. In addition to the above approach, you can use this new way of connecting signals and slots:
connect(sender, &Sender::valueChanged, receiver, &Receiver::updateValue );
Which one is more beautiful is a matter of taste. But getting used to the new version is very simple.
Now consider the advantages that it gives:
Check at compile time
You will get a compilation error if you make a mistake in the name of the signal or slot, or if the slot arguments do not match the signal arguments. This will save you time after refactoring.
In addition,
static_assert
used to show understandable errors in cases where the arguments do not match or
Q_OBJECT
omitted.
Automatic coercion of argument types
Now you can not only fear using typedef or namespaces, but also connect signals with slots that accept arguments of other types if implicit coercion is possible.
In the following example, we connect a signal that takes a
QString
as a parameter to a slot that accepts a
QVariant
. This works without problems, since
QVariant
has an implicit constructor that accepts a
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 &); };
Connect the signal with any function
As you noted in the last example,
someSlot
was declared simply as a public method, without a
slot
. Qt can call a slot directly and no longer needs introspection for this. (Although it is still needed for signals)
But now we can also connect a sinal with any function or functor:
static void someFunction() { qDebug() << "pressed"; }
This can be a very powerful feature in combination with boost or tr1 :: bind.
Anonymous functions from C ++ 11
Everything described above works with the old C ++ 98. But if you are using a compiler that supports C ++ 11, then I strongly recommend using new language features. Lambda expressions are supported at least MSVC 2010, GCC 4.5, clang 3.1. For the last two, you must specify
-std=c++0x
as a flag.
Now you can write this code:
void MyWindow::saveDocumentAs() { QFileDialog *dlg = new QFileDialog(); dlg->open(); QObject::connect(dlg, &QDialog::finished, [=](int result) { if (result) { QFile file(dlg->selectedFiles().first());
This makes writing asynchronous code very simple.