Signaux et slots
Introduction
En programmation d'interface graphique, lorsque l'on modifie un widget, on souhaite souvent qu'un autre widget en soit informé. Plus généralement, on souhaite que des objets de toute nature puissent communiquer entre eux. Par exemple, si un utilisateur clique sur un bouton « Close », on souhaite probablement que la fonction close() de la fenêtre soit appelée.
D'autres bibliothèques de développement permettent ce type de communication à l'aide de callbacks. Un callback est un pointeur vers une fonction ; ainsi, si l'on souhaite qu'une fonction de traitement signale un événement, on lui transmet un pointeur vers une autre fonction (le callback). La fonction de traitement appelle alors le callback au moment opportun. Bien qu’il existe des frameworks performants utilisant cette méthode, les callbacks peuvent manquer d’intuitivité et poser des problèmes pour garantir la cohérence des types des arguments des callbacks.
Signaux et slots
Dans Qt, nous disposons d’une alternative à la technique des callbacks : nous utilisons les signaux et les slots. Un signal est émis lorsqu’un événement particulier se produit. Les widgets de Qt possèdent de nombreux signaux prédéfinis, mais nous pouvons toujours créer des sous-classes de widgets pour y ajouter nos propres signaux. Un slot est une fonction appelée en réponse à un signal particulier. Les Qt Widgets disposent de nombreux slots prédéfinis, mais il est courant de créer des sous-classes de widgets et d’y ajouter ses propres slots afin de pouvoir gérer les signaux qui nous intéressent.
Le mécanisme des signaux et des slots est sûr du point de vue des types : la signature d’un signal doit correspondre à celle du slot qui le reçoit. (En réalité, un slot peut avoir une signature plus courte que le signal qu’il reçoit, car il peut ignorer les arguments superflus.) Les signatures étant compatibles, le compilateur peut nous aider à détecter les incompatibilités de types lors de l’utilisation de la syntaxe basée sur les pointeurs de fonction. La syntaxe SIGNAL et SLOT basée sur des chaînes de caractères détectera les incompatibilités de types lors de l'exécution. Les signaux et les slots sont faiblement couplés : une classe qui émet un signal ne sait pas et ne se soucie pas de savoir quels slots reçoivent ce signal. Le mécanisme de signaux et de slots de Qt garantit que si vous connectez un signal à un slot, ce dernier sera appelé avec les paramètres du signal au moment opportun. Les signaux et les slots peuvent prendre un nombre quelconque d’arguments de n’importe quel type. Ils sont totalement sûrs du point de vue des types.
Toutes les classes héritant de ` QObject ` ou d’une de ses sous-classes (par exemple, ` QWidget`) peuvent contenir des signaux et des slots. Les signaux sont émis par les objets lorsqu’ils changent d’état d’une manière susceptible d’intéresser d’autres objets. C’est là tout ce que fait l’objet pour communiquer. Il ne sait pas et ne se soucie pas de savoir si quelqu’un reçoit les signaux qu’il émet. Il s’agit là d’une véritable encapsulation de l’information, qui garantit que l’objet peut être utilisé comme un composant logiciel.
Les slots peuvent servir à recevoir des signaux, mais ce sont également des fonctions membres normales. Tout comme un objet ne sait pas si quelqu’un reçoit ses signaux, un slot ne sait pas s’il est connecté à des signaux. Cela garantit que des composants véritablement indépendants peuvent être créés avec Qt.
Vous pouvez connecter autant de signaux que vous le souhaitez à un même slot, et un signal peut être connecté à autant de slots que nécessaire. Il est même possible de connecter un signal directement à un autre signal. (Cela déclenchera immédiatement le deuxième signal dès que le premier sera émis.)
Ensemble, les signaux et les slots constituent un puissant mécanisme de programmation de composants.
Signaux
Les signaux sont émis par un objet lorsque son état interne a subi un changement susceptible d’intéresser le client ou le propriétaire de cet objet. Les signaux sont des fonctions d’accès public et peuvent être émis depuis n’importe où, mais nous recommandons de ne les émettre qu’à partir de la classe qui définit le signal et de ses sous-classes.
Lorsqu’un signal est émis, les slots qui y sont connectés sont généralement exécutés immédiatement, tout comme lors d’un appel de fonction normal. Dans ce cas, le mécanisme des signaux et des slots est totalement indépendant de toute boucle d’événements de l’interface graphique. L’exécution du code suivant l’instruction emit aura lieu une fois que tous les slots auront renvoyé leur résultat. La situation est légèrement différente lorsque l’on utilise ` queued connections` ; dans ce cas, le code suivant le mot-clé ` emit ` se poursuivra immédiatement, et les slots seront exécutés ultérieurement.
Si plusieurs slots sont connectés à un même signal, ceux-ci seront exécutés les uns après les autres, dans l’ordre dans lequel ils ont été connectés, lorsque le signal sera émis.
Les signaux sont générés automatiquement par le moc et ne doivent pas être implémentés dans le fichier « .cpp ».
Remarque concernant les arguments : notre expérience montre que les signaux et les slots sont plus réutilisables s’ils n’utilisent pas de types spéciaux. Si la fonction ` QScrollBar::valueChanged()` utilisait un type spécial tel que l’hypothétique `QScrollBar::Range`, elle ne pourrait être connectée qu’à des slots conçus spécifiquement pour ` QScrollBar`. Il serait alors impossible de connecter entre eux différents widgets d’entrée.
Slots
Un slot est appelé lorsqu'un signal qui lui est connecté est émis. Les slots sont des fonctions C++ classiques et peuvent être appelés normalement ; leur seule particularité est qu'ils peuvent être connectés à des signaux.
Les slots étant des fonctions membres classiques, ils obéissent aux règles habituelles du C++ lorsqu’ils sont appelés directement. Cependant, en tant que slots, elles peuvent être invoquées par n'importe quel composant, quel que soit son niveau d'accès, via une connexion signal-slot. Cela signifie qu'un signal émis par une instance d'une classe quelconque peut entraîner l'invocation d'un slot privé dans une instance d'une classe sans rapport avec la première.
Vous pouvez également définir des slots comme étant virtuels, ce qui s'est avéré très utile dans la pratique.
Par rapport aux callbacks, les signaux et les slots sont légèrement plus lents en raison de la flexibilité accrue qu’ils offrent, bien que la différence soit négligeable dans les applications réelles. En général, émettre un signal connecté à plusieurs slots est environ dix fois plus lent que d’appeler directement les récepteurs, avec des appels de fonctions non virtuelles. Il s'agit de la surcharge nécessaire pour localiser l'objet de connexion, pour parcourir en toute sécurité toutes les connexions (c'est-à-dire vérifier que les récepteurs suivants n'ont pas été détruits pendant l'émission), et pour marshaller les paramètres de manière générique. Même si dix appels de fonctions non virtuelles peuvent sembler beaucoup, cela représente une surcharge bien moindre que n’importe quelle opération de type ` new ` ou ` delete `, par exemple. Dès que vous effectuez une opération sur une chaîne, un vecteur ou une liste qui, en arrière-plan, nécessite des appels de type ` new ` ou ` delete`, la surcharge liée aux signaux et slots ne représente qu’une très faible proportion du coût total des appels de fonctions. Il en va de même chaque fois que vous effectuez un appel système dans un slot ; ou que vous appelez indirectement plus de dix fonctions. La simplicité et la flexibilité du mécanisme des signaux et des slots valent largement la surcharge, que vos utilisateurs ne remarqueront même pas.
Notez que d’autres bibliothèques définissant des variables nommées « signals » ou « slots » peuvent générer des avertissements et des erreurs de compilation lorsqu’elles sont compilées avec une application basée sur Qt. Pour résoudre ce problème, utilisez la directive ` #undef ` sur le symbole du préprocesseur incriminé.
Un petit exemple
Une déclaration de classe C++ minimale pourrait se présenter comme suit :
class Counter
{
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
void setValue(int value);
private:
int m_value;
};Une petite classe basée sur l'QObject pourrait se présenter ainsi :
#include <QObject>
class Counter : public QObject
{
Q_OBJECT
// Note. The Q_OBJECT macro starts a private section.
// To declare public members, use the 'public:' access modifier.
public:
Counter() { m_value = 0; }
int value() const { return m_value; }
public slots:
void setValue(int value);
signals:
void valueChanged(int newValue);
private:
int m_value;
};La version basée sur « QObject » possède le même état interne et fournit des méthodes publiques permettant d’accéder à cet état, mais elle prend en outre en charge la programmation par composants à l’aide de signaux et de slots. Cette classe peut signaler à l’extérieur que son état a changé en émettant un signal, « valueChanged() », et elle dispose d’un slot auquel d’autres objets peuvent envoyer des signaux.
Toutes les classes contenant des signaux ou des slots doivent mentionner ` Q_OBJECT ` en tête de leur déclaration. Elles doivent également dériver (directement ou indirectement) de ` QObject`.
Les slots sont implémentés par le programmeur d’application. Voici une implémentation possible du slot « Counter::setValue() » :
void Counter::setValue(int value)
{
if (value != m_value) {
m_value = value;
emit valueChanged(value);
}
}La ligne ` emit ` émet le signal ` valueChanged() ` à partir de l'objet, avec la nouvelle valeur en argument.
Dans l'extrait de code suivant, nous créons deux objets Counter et connectons le signal « valueChanged() » du premier objet au slot « setValue() » du second objet à l'aide de la méthode « QObject::connect() » :
Counter a, b;
QObject::connect(&a, &Counter::valueChanged,
&b, &Counter::setValue);
a.setValue(12); // a.value() == 12, b.value() == 12
b.setValue(48); // a.value() == 12, b.value() == 48valueChanged(12) L'appel à ` a.setValue(12) ` provoque l'émission d'un signal ` a ` par ` `, que ` b ` recevra dans son slot ` setValue() `, c'est-à-dire que ` b.setValue(12) ` est appelé. Ensuite, ` b ` émet le même signal ` valueChanged() `, mais comme aucun slot n'a été connecté au signal ` valueChanged() ` de ` b`, le signal est ignoré.
Notez que la fonction setValue() ne définit la valeur et n’émet le signal que si value != m_value. Cela empêche les boucles infinies en cas de connexions cycliques (par exemple, si b.valueChanged() était connecté à a.setValue()).
Par défaut, chaque connexion établie génère un signal ; deux signaux sont émis en cas de connexions en double. Vous pouvez rompre toutes ces connexions à l’aide d’un seul appel à disconnect(). Si vous passez l’ Qt::UniqueConnection type , la connexion ne sera établie que si elle n’est pas en double. S’il existe déjà un doublon (exactement le même signal vers exactement le même emplacement sur les mêmes objets), la connexion échouera et la fonction connect renverra false.
Cet exemple montre que des objets peuvent fonctionner ensemble sans avoir besoin de connaître la moindre information les uns sur les autres. Pour cela, il suffit que les objets soient connectés entre eux, ce qui peut être réalisé à l’aide de quelques appels simples à la fonction ` QObject::connect()`, ou grâce à la fonctionnalité de connexions automatiques d’uic.
Un exemple concret
Voici un exemple d'en-tête d'une classe de widget simple sans fonctions membres. L'objectif est de montrer comment vous pouvez utiliser les signaux et les slots dans vos propres applications.
#ifndef LCDNUMBER_H
#define LCDNUMBER_H
#include <QFrame>
class LcdNumber : public QFrame
{
Q_OBJECTLcdNumber Elle hérite de ` QObject`, qui contient l'essentiel des connaissances relatives aux signaux et slots, via ` QFrame ` et ` QWidget`. Elle est quelque peu similaire au widget intégré ` QLCDNumber `.
La macro Q_OBJECT est développée par le préprocesseur pour déclarer plusieurs fonctions membres qui sont implémentées par moc; si vous obtenez des erreurs de compilation du type « référence non définie à vtable pour LcdNumber », vous avez probablement oublié d’exécuter moc ou d’inclure la sortie de moc dans la commande de liaison.
public:
LcdNumber(QWidget *parent = nullptr);
signals:
void overflow();Après le constructeur de la classe et les membres de type « public », nous déclarons la classe signals. La classe LcdNumber émet un signal, overflow(), lorsqu’on lui demande d’afficher une valeur impossible.
Si le débordement ne vous préoccupe pas, ou si vous savez qu’il ne peut pas se produire, vous pouvez ignorer le signal ` overflow() `, c’est-à-dire ne le connecter à aucun slot.
Si, en revanche, vous souhaitez appeler deux fonctions d'erreur différentes en cas de débordement, il suffit de connecter le signal à deux slots distincts. Qt appellera les deux (dans l'ordre dans lequel elles ont été connectées).
public slots:
void display(int num);
void display(double num);
void display(const QString &str);
void setHexMode();
void setDecMode();
void setOctMode();
void setBinMode();
void setSmallDecimalPoint(bool point);
};
#endifUn slot est une fonction de réception utilisée pour obtenir des informations sur les changements d’état d’autres widgets. ` LcdNumber ` l’utilise, comme l’indique le code ci-dessus, pour définir le nombre affiché. Étant donné que ` display() ` fait partie de l’interface de la classe avec le reste du programme, le slot est public.
Plusieurs des programmes d’exemple relient le signal valueChanged() d’un QScrollBar au slot display(), de sorte que le chiffre affiché sur l’écran LCD indique en permanence la valeur de la barre de défilement.
Notez que la fonction `display()` est surchargée. Les signaux et slots de Qt vous offrent des connexions fortement typées. Ils sont plus sûrs au moment de la compilation que les callbacks traditionnels. Avec les callbacks, vous devriez utiliser des noms de fonctions différents et effectuer un suivi manuel des types. En revanche, avec les fonctions surchargées, vous devez spécifier la version à utiliser. La section suivante vous montre comment procéder.
Connexion à des signaux et slots surchargés
Lorsque des signaux ou des slots sont surchargés (c'est-à-dire qu'ils possèdent plusieurs versions avec des paramètres différents), vous devez spécifier explicitement la version à laquelle vous souhaitez vous connecter en utilisant la syntaxe des pointeurs de fonction. Vous pouvez utiliser qOverload() ou static_cast pour lever toute ambiguïté :
// Connect to the QString overload of QCompleter::activated(const QString &)
connect(completer, qOverload<const QString &>(&QCompleter::activated),
this, &MyClass::onActivatedText);
// Or select QLCDNumber::display(int) when connecting from QSlider::valueChanged(int)
connect(slider, &QSlider::valueChanged,
lcd, qOverload<int>(&QLCDNumber::display));
// Using static_cast (more verbose):
connect(completer, static_cast<void(QCompleter::*)(const QString &)>(&QCompleter::activated),
this, &MyClass::onActivatedText);
// Or using a lambda to call the correct overload:
connect(slider, &QSlider::valueChanged,
this, [lcd](int value) { lcd->display(value); });Gestion automatique des connexions
Qt gère automatiquement la durée de vie des connexions entre les types dérivés de ` QObject`. Lorsque l'objet émetteur ou récepteur est détruit, la connexion est automatiquement supprimée, ce qui empêche les appels vers des objets supprimés. Cela s'applique aussi bien à la syntaxe des pointeurs de fonction qu'à la syntaxe SIGNAL/SLOT basée sur des chaînes de caractères.
Pour les connexions lambda, fournissez un objet de contexte (généralement this) afin de garantir que le lambda soit déconnecté lorsque le contexte est détruit :
connect(button, &QPushButton::clicked, this, [this]{ handleClick(); });Bien que Qt empêche la transmission de signaux vers des objets entièrement détruits, des signaux peuvent tout de même être transmis pendant la destruction d’un objet, une fois que le destructeur de la classe dérivée s’est terminé mais avant d’atteindre ~QObject. Cette limitation s’applique spécifiquement aux connexions établies avec la syntaxe des pointeurs de fonction. Cela peut se produire lorsqu’un destructeur de classe de base émet des signaux auxquels la classe dérivée, déjà détruite, était connectée. Envisagez de déconnecter explicitement ces signaux dans les destructeurs si cela peut poser problème pour votre classe.
Signaux et slots avec des arguments par défaut
Les signatures des signaux et des slots peuvent contenir des arguments, et ces arguments peuvent avoir des valeurs par défaut. Prenons l’exemple de ` QObject::destroyed()` :
void destroyed(QObject* = nullptr);Lorsqu’un ` QObject ` est supprimé, il émet le signal ` QObject::destroyed()`. Nous souhaitons intercepter ce signal partout où nous pourrions avoir une référence pendante vers le ` QObject` supprimé, afin de pouvoir la nettoyer. Une signature de slot appropriée pourrait être :
void objectDestroyed(QObject* obj = nullptr);Pour associer le signal au slot, nous utilisons ` QObject::connect()`. Il existe plusieurs façons d'associer un signal à un slot. La première consiste à utiliser des pointeurs de fonction :
connect(sender, &QObject::destroyed, this, &MyObject::objectDestroyed);L'utilisation de ` QObject::connect()` avec des pointeurs de fonction présente plusieurs avantages. Tout d'abord, cela permet au compilateur de vérifier que les arguments du signal sont compatibles avec ceux du slot. Les arguments peuvent également être convertis implicitement par le compilateur, si nécessaire.
Vous pouvez également établir une connexion avec des foncteurs ou des lambdas C++11 :
connect(sender, &QObject::destroyed, this, [=](){ this->m_objects.remove(sender); });Dans ces deux cas, nous fournissons ` this ` comme contexte dans l'appel à `connect()`. L'objet de contexte fournit des informations sur le thread dans lequel le récepteur doit être exécuté. Ceci est important, car la fourniture du contexte garantit que le récepteur est exécuté dans le thread de contexte.
Le lambda sera déconnecté lorsque l'émetteur ou le contexte sera détruit. Vous devez veiller à ce que tous les objets utilisés à l'intérieur du foncteur soient encore actifs lorsque le signal est émis.
L’autre façon de connecter un signal à un slot consiste à utiliser ` QObject::connect()` ainsi que les macros ` SIGNAL ` et ` SLOT `. La règle à respecter concernant l’inclusion ou non d’arguments dans les macros ` SIGNAL() ` et ` SLOT() `, lorsque les arguments ont des valeurs par défaut, est la suivante : la signature transmise à la macro ` SIGNAL() ` ne doit pas comporter moins d’arguments que la signature transmise à la macro ` SLOT() `.
Toutes ces solutions fonctionneraient :
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));Mais celui-ci ne fonctionnera pas :
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));...car le slot attendra une valeur « QObject » que le signal n'enverra pas. Cette connexion générera une erreur d'exécution.
Notez que les arguments du signal et du slot ne sont pas vérifiés par le compilateur lors de l'utilisation de cette surcharge de la méthode QObject::connect().
Utilisation avancée des signaux et des slots
Dans les cas où vous pourriez avoir besoin d’informations sur l’émetteur du signal, Qt fournit la fonction ` QObject::sender()`, qui renvoie un pointeur vers l’objet ayant émis le signal.
Les expressions lambda constituent un moyen pratique de passer des arguments personnalisés à un slot :
connect(action, &QAction::triggered, engine,
[=]() { engine->processAction(action->text()); });Utilisation de Qt avec des signaux et des slots tiers
Il est possible d'utiliser Qt avec un mécanisme de signaux et de slots tiers. Vous pouvez même utiliser les deux mécanismes dans un même projet. Pour ce faire, ajoutez ce qui suit dans votre fichier de projet CMake :
target_compile_definitions(my_app PRIVATE QT_NO_KEYWORDS)Dans un fichier de projet qmake (.pro), vous devez écrire :
CONFIG += no_keywordsCela indique à Qt de ne pas définir les mots-clés moc « signals », « slots » et « emit », car ces noms seront utilisés par une bibliothèque tierce, par exemple Boost. Ensuite, pour continuer à utiliser les signaux et slots Qt avec l'option « no_keywords », il suffit de remplacer toutes les occurrences des mots-clés moc Qt dans vos sources par les macros Qt correspondantes : Q_SIGNALS (ou Q_SIGNAL), Q_SLOTS (ou Q_SLOT) et Q_EMIT.
Signaux et slots dans les bibliothèques basées sur Qt
L’API publique des bibliothèques basées sur Qt doit utiliser les mots-clés Q_SIGNALS et Q_SLOTS au lieu de signals et slots. Sinon, il est difficile d’utiliser une telle bibliothèque dans un projet qui définit QT_NO_KEYWORDS.
Pour appliquer cette restriction, le créateur de la bibliothèque peut définir la directive de préprocesseur QT_NO_SIGNALS_SLOTS_KEYWORDS lors de la compilation de la bibliothèque.
Cette définition exclut les signaux et les slots sans affecter la possibilité d’utiliser d’autres mots-clés spécifiques à Qt dans l’implémentation de la bibliothèque.
Voir également QLCDNumber, QObject::connect(), le système de méta-objets et le système de propriétés de Qt.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.