I. Article original▲
Cet article est une traduction de Signals and Slots in Qt5 écrit par Olivier Goffart paru le 12 avril 2012.
II. Ancienne syntaxe▲
Voici la syntaxe actuelle de connexion d'un signal à un slot :
connect
(sender, SIGNAL
(valueChanged(QString
, QString
)),
receiver, SLOT
(updateValue(QString
)) );
Les macros SIGNAL et SLOT convertissent leurs arguments en chaînes de caractères. Ensuite, QObject::connect() les comparera avec les chaînes récupérées lors de l'analyse des données générées par l'outil moc.
I-A. Quels sont les problèmes posés par cette syntaxe ?▲
Bien que cette syntaxe fonctionne dans la grande majorité des cas, elle présente deux problèmes :
- il n'y a aucune vérification à la compilation : toutes les vérifications sont faites à l'exécution lors de la comparaison des chaînes de caractères. Cela veut dire qu'en cas de typo dans le nom d'un signal ou d'un slot, le code compilera mais la jonction ne se fera pas : cela sera signalé à l'exécution via un message sur la sortie standard ;
- puisque le travail s'effectue sur des chaînes de caractères, les noms des types des slots doivent exactement correspondre aux noms des types des signaux. Ils doivent par ailleurs être les mêmes que dans le fichier d'en-tête et lors de la connexion à proprement parler. Cela signifie que l'utilisation de typedef ou des espaces de noms pourra poser problème.
III. Nouvelle syntaxe : avec des pointeurs de fonctions▲
Avec Qt 5, une syntaxe alternative est disponible. La précédente fonctionne toujours, mais une nouvelle syntaxe de connexion est disponible :
connect
(sender, &
Sender::
valueChanged,
receiver, &
Receiver::
updateValue
);
Outre toute considération esthétique, cette nouvelle syntaxe corrige certains de ces problèmes.
II-A. Vérifications à la compilation▲
En cas de typo dans le nom d'un signal ou d'un slot, le compilateur le signalera, de même si les arguments entre le signal et le slot ne correspondent pas.
Cela correspondra très certainement à une économie de temps, particulièrement en cas de réorganisation du code et de modification des noms ou arguments des signaux et slots.
Des efforts ont été faits, notamment afin d'obtenir de belles erreurs à la compilation grâce à static_assert, si les arguments venaient à ne pas correspondre ou en cas d'oubli de la macro Q_OBJECT.
II-B. Conversion automatique du type des arguments▲
Non seulement, maintenant, il est possible d'utiliser typedef ou des espaces de noms correctement, mais aussi de connecter un signal à un slot qui prend un argument de type différent si une conversion implicite est possible.
L'exemple suivant connecte un signal dont le seul argument est une QString à un slot qui prend un QVariant. Cela est possible étant donné que la classe QVariant possède un constructeur implicite prenant un QString comme seul argument.
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
&
);
}
;
II-C. Connexion à n'importe quelle fonction▲
Dans l'exemple précédent, le slot a simplement été déclaré en tant que public et non en tant que slot. Qt se sert directement du pointeur de fonction et ne nécessitera plus l'introspection du moc, bien que ce dernier soit toujours nécessaire pour gérer les signaux.
Il est aussi possible de connecter un slot à n'importe quelle fonction ou foncteur :
static
void
someFunction() {
qDebug
() <<
"appuyé"
;
}
// ailleurs
QObject
::
connect
(button, &
QPushButton
::
clicked, someFunction);
Cela peut devenir extrêmement puissant quand vous associez cela à boost ou à tr1::bind.
II-D. Les expressions lambda de C++11▲
Tout ce qui est présenté ici fonctionne parfaitement avec un bon vieux compilateur C++98. Néanmoins, s'il supporte C++11, les autres nouvelles fonctionnalités du langage sont toujours accessibles. Les lambdas sont au moins par MSVC 2010, GCC 4.5 et clang 3.1. Pour ces deux derniers, il faudra toujours spécifier -std=c++0x en tant que paramètre.
Vous pouvez alors écrire un code tel que :
void
MyWindow::
saveDocumentAs() {
QFileDialog
*
dlg =
new
QFileDialog
();
dlg->
open();
QObject
::
connect
(dlg, &
QDialog
::
finished, [=
](int
result) {
if
(result) {
QFile
file(dlg->
selectedFiles().first());// sauvegarder les documents ici
}
dlg->
deleteLater();
}
);
}
Cela facilite l'écriture de code asynchrone.
IV. Remerciements▲
Au nom de toute l'équipe Qt, j'aimerais adresser le plus grand remerciement à KDAB pour nous avoir autorisés à traduire cet article !
Je tiens à remercier Thibaut Cuvelier pour ses conseils et relecture.