Signals et slots dans Qt : syntaxe moderne, Q_OBJECT et pièges de connexion à éviter

Dans Qt, les signals et slots sont le mécanisme central de communication entre objets. Un bouton cliqué, une valeur modifiée, un traitement terminé : au lieu d’appeler directement une autre classe, un objet émet un signal et Qt appelle les fonctions connectées. Le principe est simple, mais les détails comptent vite, surtout avec QObject::connect, la macro Q_OBJECT, les surcharges et les connexions directes ou différées.

Le rôle des signals et slots dans Qt

Un signal est une notification émise lorsqu’un événement ou un changement d’état survient. Il ne porte pas de logique métier, il annonce un fait. Un objet peut signaler qu’une valeur a changé, qu’une requête est terminée ou qu’un utilisateur a déclenché une action dans l’interface.

Comprendre les Qt signals

Un slot est une fonction appelée en réponse à ce signal. Il peut appartenir au même objet, à un autre objet, à un widget ou à une classe métier. L’intérêt tient au couplage faible : l’émetteur n’a pas besoin de connaître précisément le récepteur. Il émet, Qt transmet.

Ce modèle remplace souvent des callbacks trop rigides. Avec une callback classique, l’objet appelant doit stocker une fonction ou dépendre d’un contrat plus direct. Avec les signals et slots, un même signal peut être connecté à plusieurs slots, plusieurs signaux peuvent alimenter un même slot, et un signal peut même être connecté à un autre signal pour relayer une notification.

Le flux à garder en tête

Le chemin mental reste le même : objet émetteur, signal, connexion, slot du récepteur. Par exemple, un bouton émet clicked(), la connexion cible une méthode, puis cette méthode s’exécute. Cette séparation rend les composants plus réutilisables : un widget peut fonctionner sans savoir quelle fenêtre, quel service ou quel contrôleur l’écoute.

On peut voir le signal comme une lumière allumée dans une pièce sombre. Il n’oblige personne à venir, mais il rend un événement visible pour tous ceux qui surveillent cette lumière. Cette image aide à éviter une erreur fréquente : un signal ne doit pas faire le travail à la place du slot. Il signale un changement d’état, puis les objets intéressés décident quoi faire. En pratique, cela pousse à nommer les signaux comme des faits observables, par exemple downloadFinished ou valueChanged, plutôt que comme des ordres tels que updateDatabaseNow.

Les règles indispensables : QObject, Q_OBJECT et moc

Le mécanisme signals et slots repose sur le meta-object system de Qt. Pour déclarer ses propres signaux et slots dans une classe, celle-ci doit généralement hériter de QObject ou d’une classe qui en dérive, comme QWidget. Elle doit aussi contenir la macro Q_OBJECT dans sa déclaration.

LIRE AUSSI  Créer un Singleton sur iPhone en Cocoa / Objective-C
Schéma des Qt signals montrant le flux objet émetteur, signal, connexion et slot du récepteur
Schéma des Qt signals montrant le flux objet émetteur, signal, connexion et slot du récepteur

Cette macro permet au moc, le Meta-Object Compiler, de générer le code nécessaire aux signaux, à l’introspection et à certaines fonctionnalités dynamiques. Si elle manque, les erreurs peuvent être déroutantes : connexion impossible, signal non reconnu ou problème à l’édition de liens. Dans un projet Qt configuré correctement, le moc est lancé automatiquement, mais il faut respecter la structure de classe attendue.

Déclarer un signal personnalisé

Un signal se déclare dans une section signals:. Il est généralement de type void et n’a pas d’implémentation manuelle : le moc s’en charge. Un exemple conceptuel serait : signals: void valueChanged(int newValue);. Pour l’émettre depuis la classe, on utilise emit : emit valueChanged(value);.

Le mot-clé emit améliore surtout la lisibilité. Il indique clairement au lecteur qu’une notification Qt est déclenchée. Les paramètres du signal sont transmis aux slots compatibles. Un slot peut accepter moins d’arguments que le signal dans certains cas, mais il doit rester cohérent avec la signature attendue par la connexion utilisée.

Définir un slot sans le mystifier

Un slot est une fonction membre appelée par Qt. Historiquement, on utilisait des sections comme public slots:, protected slots: ou private slots:. Avec les syntaxes modernes, un slot peut aussi être une fonction membre normale, une lambda, une fonction libre ou un foncteur, selon le type de connexion choisi.

Cette souplesse est utile, mais elle ne dispense pas d’une règle simple : un slot doit rester lisible et limité. S’il déclenche à son tour plusieurs signaux, modifie beaucoup d’état ou relance la même chaîne d’événements, le diagnostic devient difficile.

Connecter un signal à un slot : ancienne et nouvelle syntaxe

La fonction centrale est QObject::connect. Elle associe un signal d’un objet émetteur à une fonction appelée lorsque le signal est émis. La différence entre les styles de connexion tient surtout à la sécurité de typage et au moment où les erreurs sont détectées.

Syntaxe Exemple d’usage À privilégier quand
SIGNAL/SLOT connect(a, SIGNAL(valueChanged(int)), b, SLOT(setValue(int))) Maintenance de code Qt 4 ou ancien code existant
Pointeurs de fonctions connect(a, &A::valueChanged, b, &B::setValue) Qt 5 et Qt 6, avec vérification plus sûre à la compilation
qOverload qOverload<int>(&QComboBox::currentIndexChanged) Signaux ou slots surchargés
Lambda connect(button, &QPushButton::clicked, this, [this]{ save(); }) Petite réaction locale, adaptation d’arguments, code très ciblé
LIRE AUSSI  UI-View : 16 ports RF et 3 niveaux de zoom pour maîtriser votre réseau APRS

La syntaxe ancienne avec SIGNAL et SLOT encode les signatures sous forme textuelle. Elle reste familière dans les projets Qt 4, mais certaines erreurs ne sont visibles qu’à l’exécution. La syntaxe moderne, courante en Qt 5 et Qt 6, utilise des pointeurs de fonctions membres : le compilateur peut détecter plus tôt les incompatibilités de type.

Gérer les surcharges proprement

Les classes Qt proposent parfois plusieurs signaux de même nom avec des paramètres différents. Dans ce cas, le compilateur ne sait pas toujours quelle surcharge choisir. On utilise alors qOverload, ou selon les besoins QOverload::of, QConstOverload ou QNonConstOverload. Le but est de lever l’ambiguïté sans revenir à une connexion textuelle fragile.

Les lambdas sont très pratiques pour transformer une signature ou capturer un contexte. Elles doivent cependant rester courtes. Si une lambda dépasse quelques lignes ou contient une logique métier durable, mieux vaut créer une méthode nommée. Le code sera plus facile à tester et à retrouver.

Comprendre les types de connexion Qt

Quand un signal est émis, Qt choisit comment appeler le slot selon le type de connexion. Par défaut, Qt::AutoConnection convient dans la majorité des cas : si l’émetteur et le récepteur vivent dans le même thread, l’appel est généralement direct ; sinon, Qt bascule vers une exécution différée via la file d’événements du thread cible.

Type Comportement Point d’attention
Qt::DirectConnection Le slot est appelé immédiatement dans le thread de l’émetteur À éviter si le récepteur n’est pas thread-safe
Qt::QueuedConnection L’appel est placé dans la file d’événements du récepteur Les arguments doivent pouvoir être transportés par le système Qt
Qt::BlockingQueuedConnection L’émetteur attend la fin du slot Risque de blocage si mal utilisé
Qt::UniqueConnection Évite de dupliquer une même connexion Utile contre les appels multiples accidentels
Qt::SingleShotConnection Connexion utilisée une seule fois Pratique pour une réaction ponctuelle

Une connexion queued décale l’exécution dans le temps. C’est utile entre threads ou pour ne pas interrompre brutalement un flux d’exécution, mais cela change le raisonnement. Au moment où le slot s’exécute, l’état de l’application peut avoir évolué. Il faut donc éviter de supposer que tout est identique à l’instant de l’émission.

Côté performance, le mécanisme signals et slots ajoute une couche d’indirection. Il est parfois présenté comme environ 10 fois plus lent qu’un appel de fonction direct, mais cette comparaison doit être lue avec recul. Pour la plupart des interfaces et communications applicatives, ce coût reste négligeable face aux allocations, aux entrées-sorties, aux traitements graphiques ou aux accès réseau. Il devient surtout pertinent dans des boucles très serrées.

LIRE AUSSI  SeaPerch : Fabriquer facilement son Robot Sous-Marin (ROV)

Bonnes pratiques et erreurs fréquentes

La première bonne pratique consiste à nommer les signaux comme des événements passés ou des changements observés : statusChanged, itemSelected, finished. Cela évite de transformer le signal en commande déguisée et garde une architecture plus claire.

  • Éviter les connexions dupliquées : si une méthode d’initialisation peut être appelée plusieurs fois, utilisez Qt::UniqueConnection ou centralisez les connexions.
  • Déconnecter quand c’est nécessaire : disconnect permet de couper une relation devenue invalide ou temporaire.
  • Surveiller les cycles : un slot qui modifie une valeur peut réémettre le signal qui l’a déclenché et provoquer une boucle infinie.
  • Préférer la syntaxe moderne : elle rend les erreurs de signature plus visibles à la compilation.
  • Limiter les lambdas capturantes : attention aux pointeurs capturés et à la durée de vie des objets.

Un exemple de raisonnement simple

Supposons une classe Counter qui possède une valeur interne. Quand setValue(int) reçoit une valeur différente, elle met à jour l’état puis émet valueChanged(int). Le détail important est la condition “différente” : sans elle, chaque appel peut déclencher une notification inutile, voire une boucle si un autre objet renvoie la même valeur.

Ce modèle donne une règle pratique : un slot qui modifie un état observable doit vérifier si le changement est réel avant d’émettre à son tour. Cette simple garde rend les graphes de signaux plus stables, surtout dans les formulaires, les vues synchronisées ou les modèles partagés.

En résumé, les Qt signals sont puissants parce qu’ils combinent découplage, expressivité et sécurité de typage, à condition de respecter le socle QObject, Q_OBJECT, moc et une syntaxe de connexion adaptée. La syntaxe moderne, les connexions explicites et quelques garde-fous contre les doublons suffisent souvent à transformer un code événementiel fragile en architecture Qt lisible et maintenable.

Élise Prévost-Lemercier

Partager cet article

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut