Signaux et emplacements
Introduction
Dans la programmation d'interfaces graphiques, lorsque nous modifions un widget, nous voulons souvent qu'un autre widget en soit informé. Plus généralement, nous voulons que les objets de toute nature puissent communiquer entre eux. Par exemple, si un utilisateur clique sur un bouton Close, nous voulons probablement que la fonction close() de la fenêtre soit appelée.
D'autres boîtes à outils réalisent ce type de communication à l'aide de rappels. Un callback est un pointeur vers une fonction. Ainsi, si vous souhaitez qu'une fonction de traitement vous informe d'un événement, vous devez passer un pointeur vers une autre fonction (le callback) à la fonction de traitement. La fonction de traitement appelle alors la fonction de rappel le cas échéant. Bien qu'il existe des frameworks efficaces utilisant cette méthode, les callbacks peuvent être peu intuitifs et peuvent souffrir de problèmes pour assurer la correction du type des arguments des callbacks.
Signaux et emplacements
Dans Qt, nous disposons d'une alternative à la technique du rappel : Nous utilisons des signaux et des slots. Un signal est émis lorsqu'un événement particulier se produit. Les widgets de Qt Widgets ont de nombreux signaux prédéfinis, mais nous pouvons toujours sous-classer les widgets pour leur ajouter nos propres signaux. Un slot est une fonction qui est appelée en réponse à un signal particulier. Les widgets de Qt Widgets ont de nombreux slots prédéfinis, mais il est courant de sous-classer les widgets et d'y ajouter ses propres slots afin de pouvoir gérer les signaux qui vous intéressent.
Le mécanisme des signaux et des slots est sans danger pour les types : La signature d'un signal doit correspondre à la signature de l'emplacement de réception. (En fait, un slot peut avoir une signature plus courte que le signal qu'il reçoit car il peut ignorer des arguments supplémentaires). Comme les signatures sont compatibles, le compilateur peut nous aider à détecter les erreurs de type lors de l'utilisation de la syntaxe basée sur les pointeurs de fonction. La syntaxe SIGNAL et SLOT basée sur les chaînes de caractères détectera les incohérences de type au moment de l'exécution. Les signaux et les slots sont faiblement couplés : Une classe qui émet un signal ne sait pas quels emplacements reçoivent le signal et ne s'en préoccupe pas. 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 bon moment. Les signaux et les slots peuvent prendre n'importe quel nombre d'arguments de n'importe quel type. Ils sont totalement sûrs.
Toutes les classes qui héritent de QObject ou de l'une de ses sous-classes (par exemple, QWidget) peuvent contenir des signaux et des slots. Les signaux sont émis par les objets lorsqu'ils modifient leur état d'une manière susceptible d'intéresser d'autres objets. C'est tout ce que fait l'objet pour communiquer. Il ne sait pas si quelque chose reçoit les signaux qu'il émet et ne s'en préoccupe pas. Il s'agit d'une véritable encapsulation de l'information, qui garantit que l'objet peut être utilisé comme un composant logiciel.
Les slots peuvent être utilisés pour recevoir des signaux, mais ce sont également des fonctions membres normales. Tout comme un objet ne sait pas si quelque chose reçoit ses signaux, un slot ne sait pas s'il a des signaux connectés à lui. Cela permet de créer des composants réellement indépendants avec Qt.
Vous pouvez connecter autant de signaux que vous le souhaitez à un seul 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. (Le second signal sera émis immédiatement lorsque le premier sera émis).
Ensemble, les signaux et les slots constituent un puissant mécanisme de programmation des composants.
Les signaux
Les signaux sont émis par un objet lorsque son état interne a changé d'une manière qui pourrait intéresser le client ou le propriétaire de l'objet. Les signaux sont des fonctions d'accès public et peuvent être émis de 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 lui sont connectés sont généralement exécutés immédiatement, comme 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énement de l'interface graphique. L'exécution du code suivant l'instruction emit aura lieu une fois que tous les slots auront été renvoyés. La situation est légèrement différente lorsque l'on utilise queued connections; dans ce cas, le code qui suit le mot-clé emit continuera immédiatement, et les slots seront exécutés plus tard.
Si plusieurs slots sont connectés à un signal, les slots seront exécutés l'un après l'autre, dans l'ordre où ils ont été connectés, lorsque le signal est émis.
Les signaux sont générés automatiquement par le moc et ne doivent pas être implémentés dans le fichier .cpp.
Une remarque sur 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 QScrollBar::valueChanged() devait utiliser un type spécial tel que l'hypothétique QScrollBar::Range, il ne pourrait être connecté qu'à des slots conçus spécifiquement pour QScrollBar. Il serait impossible de connecter différents widgets d'entrée ensemble.
Emplacements
Un slot est appelé lorsqu'un signal qui lui est connecté est émis. Les slots sont des fonctions C++ normales et peuvent être appelées normalement ; leur seule particularité est que des signaux peuvent leur être connectés.
Comme les slots sont des fonctions membres normales, ils suivent les règles normales du C++ lorsqu'ils sont appelés directement. Toutefois, en tant que slots, ils peuvent être invoqués 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 arbitraire peut provoquer l'invocation d'un slot privé dans une instance d'une classe non apparentée.
Vous pouvez également définir des slots virtuels, ce qui s'est avéré très utile dans la pratique.
Comparés 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 pour les applications réelles soit insignifiante. En général, l'émission d'un signal connecté à certains slots est environ dix fois plus lente que l'appel direct des récepteurs, avec des appels de fonctions non virtuelles. Il s'agit du surcoût nécessaire pour localiser l'objet de connexion, pour itérer en toute sécurité sur toutes les connexions (c'est-à-dire vérifier que les récepteurs suivants n'ont pas été détruits pendant l'émission) et pour rassembler tous les paramètres de manière générique. Même si dix appels de fonctions non virtuelles peuvent sembler beaucoup, il s'agit d'une surcharge bien moindre que n'importe quelle opération new ou delete, par exemple. Dès que vous effectuez une opération sur une chaîne de caractères, un vecteur ou une liste qui nécessite new ou delete, les signaux et les slots ne représentent qu'une très faible proportion du coût total de l'appel de fonction. Il en va de même lorsque 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 bien ce surcoût, que vos utilisateurs ne remarqueront même pas.
Notez que d'autres bibliothèques qui définissent des variables appelées signals ou slots peuvent provoquer des avertissements et des erreurs du compilateur lorsqu'elles sont compilées avec une application basée sur Qt. Pour résoudre ce problème, #undef le symbole de préprocesseur incriminé.
Un petit exemple
Une déclaration de classe C++ minimale pourrait se lire 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 QObject pourrait se lire :
#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 pour accéder à l'état, mais elle prend également en charge la programmation des composants à l'aide de signaux et d'emplacements. Cette classe peut indiquer au monde extérieur que son état a changé en émettant un signal, valueChanged(), et elle dispose d'un emplacement auquel d'autres objets peuvent envoyer des signaux.
Toutes les classes qui contiennent 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 mis en œuvre par le programmeur de l'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 comme 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 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() == 48
L'appel de a.setValue(12) fait que a émet un signal valueChanged(12), 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 emplacement n'a été connecté au signal valueChanged() de b, le signal est ignoré.
Notez que la fonction setValue() ne fixe la valeur et n'émet le signal que si value != m_value est appelé. Cela permet d'éviter les boucles infinies dans le cas de connexions cycliques (par exemple, si b.valueChanged() était connecté à a.setValue()).
Par défaut, un signal est émis pour chaque connexion que vous établissez ; deux signaux sont émis pour les connexions dupliquées. Vous pouvez rompre toutes ces connexions avec un seul appel à disconnect(). Si vous passez l'option Qt::UniqueConnection type , la connexion ne sera établie que s'il ne s'agit pas d'un doublon. S'il existe déjà un doublon (même signal à la même place sur les mêmes objets), la connexion échouera et connect renverra false.
Cet exemple illustre le fait que des objets peuvent travailler ensemble sans avoir besoin de connaître la moindre information les uns sur les autres. Pour ce faire, il suffit de connecter les objets entre eux, ce qui peut être réalisé par de simples appels à la fonction QObject::connect() ou par la fonction de connexion automatique d'uic.
Un exemple concret
Voici un exemple de l'en-tête d'une simple classe de widget 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_OBJECT
LcdNumber hérite de QObject, qui possède la plupart des connaissances en matière de signaux et d'emplacements, par l'intermédiaire de QFrame et QWidget. Il 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 mises en œuvre par moc; si vous obtenez des erreurs de compilation du type "undefined reference to vtable for LcdNumber", vous avez probablement oublié d'exécuter le moc ou d'inclure la sortie du moc dans la commande link.
public: LcdNumber(QWidget *parent = nullptr); signals: void overflow();
Après le constructeur de la classe et les membres public, nous déclarons la classe signals. La classe LcdNumber émet un signal, overflow(), lorsqu'on lui demande d'afficher une valeur impossible.
Si vous ne vous souciez pas du débordement, ou si vous savez que le débordement ne peut pas se produire, vous pouvez ignorer le signal overflow(), c'est-à-dire ne pas le connecter à un slot.
Si par contre vous voulez appeler deux fonctions d'erreur différentes lorsque le nombre déborde, connectez simplement le signal à deux slots différents. Qt les appellera toutes les deux (dans l'ordre où 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); }; #endif
Un 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é. Comme display() fait partie de l'interface de la classe avec le reste du programme, le slot est public.
Plusieurs des exemples de programmes connectent le signal valueChanged() d'un QScrollBar à l'emplacement display(), de sorte que le numéro de l'écran LCD affiche en permanence la valeur de la barre de défilement.
Notez que display() est surchargé. Les signaux et les slots de Qt permettent des connexions fortement typées. Ils sont plus sûrs au moment de la compilation que les rappels traditionnels. Avec les callbacks, vous auriez besoin de noms de fonctions différents et d'un suivi manuel des types. Mais avec les fonctions surchargées, vous devez spécifier la version à utiliser. La section suivante vous montre comment procéder.
Connexion aux signaux surchargés et aux slots
Lorsque des signaux ou des slots sont surchargés (ils ont plusieurs versions avec des paramètres différents), vous devez spécifier explicitement la version à laquelle vous souhaitez vous connecter en utilisant la syntaxe du pointeur de fonction. Vous pouvez utiliser qOverload() ou static_cast pour désambiguïser :
// Connect to the int overload of QComboBox::currentIndexChanged(int) connect(comboBox, qOverload<int>(&QComboBox::currentIndexChanged), this, &MyClass::handleIndexChanged); // 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(comboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &MyClass::handleIndexChanged); // 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 à des objets supprimés. Cela s'applique à la fois à la syntaxe du pointeur de fonction et à la syntaxe SIGNAL/SLOT basée sur des chaînes de caractères.
Pour les connexions lambda, il convient de fournir un objet de contexte (généralement this) afin de s'assurer que la lambda est déconnectée lorsque le contexte est détruit :
connect(button, &QPushButton::clicked, this, [this]{ handleClick(); });
Bien que Qt XML se prémunisse contre la transmission de signaux à des objets entièrement détruits, des signaux peuvent toujours être transmis pendant la destruction de l'objet après la fin du destructeur d'une classe dérivée, mais avant d'atteindre ~QObject. Cette limitation s'applique spécifiquement aux connexions réalisées avec la syntaxe du pointeur de fonction. Elle 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 être problématique pour votre classe.
Signaux et slots avec arguments par défaut
Les signatures des signaux et des slots peuvent contenir des arguments, et les arguments peuvent avoir des valeurs par défaut. Prenons l'exemple de QObject::destroyed() :
void destroyed(QObject* = nullptr);
Lorsqu'un site QObject est supprimé, il émet le signal QObject::destroyed(). Nous voulons attraper ce signal, partout où nous pourrions avoir une référence pendante à l'adresse QObject supprimée, afin de pouvoir la nettoyer. Une signature de slot appropriée pourrait être :
void objectDestroyed(QObject* obj = nullptr);
Pour connecter le signal au slot, nous utilisons QObject::connect(). Il existe plusieurs façons de connecter 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 les arguments du slot. Les arguments peuvent également être implicitement convertis par le compilateur, si nécessaire.
Vous pouvez également vous connecter à 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 contexte fournit des informations sur le thread dans lequel le récepteur doit être exécuté. C'est important, car le fait de fournir le contexte garantit que le récepteur est exécuté dans le fil d'exécution du contexte.
La lambda sera déconnectée lorsque l'émetteur ou le contexte seront détruits. Vous devez veiller à ce que tous les objets utilisés dans le foncteur soient toujours en vie lorsque le signal est émis.
L'autre façon de connecter un signal à un slot est d'utiliser QObject::connect() et les macros SIGNAL et SLOT. La règle concernant l'inclusion ou non d'arguments dans les macros SIGNAL() et SLOT(), si les arguments ont des valeurs par défaut, est que la signature passée à la macro SIGNAL() ne doit pas avoir moins d'arguments que la signature passée à la macro SLOT().
Tout cela fonctionnerait :
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 celle-ci ne fonctionnera pas :
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed(QObject*)));
...parce que le slot attendra un QObject que le signal n'enverra pas. Cette connexion signalera une erreur d'exécution.
Notez que les arguments signal et slot ne sont pas vérifiés par le compilateur lors de l'utilisation de la surcharge QObject::connect().
Utilisation avancée des signaux et des slots
Pour les cas où vous avez besoin d'informations sur l'émetteur du signal, Qt XML fournit la fonction QObject::sender(), qui renvoie un pointeur sur l'objet qui a envoyé le signal.
Les expressions lambda sont 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 le même projet. Pour ce faire, écrivez 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_keywords
Cela indique à Qt de ne pas définir les mots-clés moc signals, slots, et emit, parce que ces noms seront utilisés par une bibliothèque tierce, par exemple Boost. Ensuite, pour continuer à utiliser les signaux et les slots de Qt avec le drapeau no_keywords, il suffit de remplacer toutes les utilisations des mots-clés moc de 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 devrait 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 le préprocesseur QT_NO_SIGNALS_SLOTS_KEYWORDS lors de la construction de la bibliothèque.
Cette définition exclut les signaux et les slots sans affecter l'utilisation d'autres mots-clés spécifiques à Qt dans l'implémentation de la bibliothèque.
Voir aussi QLCDNumber, QObject::connect(), Meta-Object System, et Qt's Property System.
© 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.