Les signaux et slots avec Qt 5

De nouvelles possibilités

Olivier Goffart

Traducteur

Arnold Dumas

Cela fait maintenant plusieurs mois que Qt 5 est disponible. Cette dernière mouture du framework offre une nouvelle syntaxe de connexion pour les signaux et slots.

Commentez

- Titre : Les signaux et slots avec Qt 5

- Auteur : Olivier Goffart

- Traducteur : Arnold Dumas

- Parution : 28 septembre 2013

- Mise à jour : 16 octobre 2013

- Licence : Copyright ® 2012 Olivier Goffart. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Article original

- II. Ancienne syntaxe

- I-A. Quels sont les problèmes posés par cette syntaxe ?

- III. Nouvelle syntaxe : avec des pointeurs de fonctions

- II-A. Vérifications à la compilation

- II-B. Conversion automatique du type des arguments

- II-C. Connexion à n'importe quelle fonction

- II-D. Les expressions lambda de C++11

- IV. Remerciements

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.