Pourquoi Qt utilise-t-il Moc pour les signaux et les slots ?
Les modèles (templates) sont un mécanisme intégré au C++ qui permet au compilateur de générer du code à la volée, en fonction du type des arguments passés. À ce titre, les modèles présentent un grand intérêt pour les créateurs de frameworks, et nous utilisons effectivement des modèles avancés à de nombreux endroits dans Qt. Il existe toutefois des limites : Il y a des choses que l’on peut facilement exprimer avec des modèles, et d’autres qui sont impossibles à exprimer avec des modèles. Une classe de conteneur vectoriel générique est facile à exprimer, même avec une spécialisation partielle pour les types de pointeurs, tandis qu’une fonction qui configure une interface utilisateur graphique à partir d’une description XML fournie sous forme de chaîne de caractères ne peut pas être exprimée sous forme de modèle. Et puis il existe une zone grise entre les deux. Des choses que l’on peut « bricoler » avec des modèles au détriment de la taille du code, de la lisibilité, de la portabilité, de la facilité d’utilisation, de l’extensibilité, de la robustesse et, en fin de compte, de l’élégance de la conception. Tant les modèles que le préprocesseur C peuvent être poussés à leurs limites pour réaliser des choses incroyablement intelligentes et époustouflantes. Mais ce n’est pas parce que ces choses sont possibles qu’elles constituent nécessairement le bon choix de conception. Le code, malheureusement, n’est pas destiné à être publié dans des livres, mais à être compilé avec de vrais compilateurs sur de vrais systèmes d’exploitation.
Voici quelques raisons pour lesquelles Qt utilise moc :
L’importance de la syntaxe
La syntaxe n’est pas qu’une simple « couche de sucre » : la syntaxe que nous utilisons pour exprimer nos algorithmes peut considérablement influencer la lisibilité et la maintenabilité de notre code. La syntaxe utilisée pour les signaux et les slots de Qt s’est avérée très efficace dans la pratique. Elle est intuitive, simple à utiliser et facile à lire. Les personnes qui apprennent Qt trouvent que cette syntaxe les aide à comprendre et à utiliser le concept des signaux et des slots, malgré sa nature très abstraite et générique. Cela aide les programmeurs à concevoir correctement leur application dès le début, sans même avoir à réfléchir aux modèles de conception.
Les générateurs de code sont utiles
Le Meta-Object Compiler ( moc, MOC) de Qt offre un moyen élégant d’aller au-delà des fonctionnalités du langage compilé. Pour ce faire, il génère du code C++ supplémentaire pouvant être compilé par n’importe quel compilateur C++ standard. L’ moc lit les fichiers source C++. S’il trouve une ou plusieurs déclarations de classe contenant la macro « Q_OBJECT », il génère un autre fichier source C++ contenant le code méta-objet correspondant à ces classes. Le fichier source C++ généré par l’ moc doit être compilé et lié à l’implémentation de la classe (ou il peut être #included dans le fichier source de la classe). En règle générale, l’ moc n’est pas appelée manuellement, mais automatiquement par le système de construction ; elle ne nécessite donc aucun effort supplémentaire de la part du programmeur.
moc n’est pas le seul générateur de code utilisé par Qt. Un autre exemple marquant est uic (User Interface Compiler). Il prend une description d’interface utilisateur au format XML et génère du code C++ qui configure le formulaire. En dehors de Qt, les générateurs de code sont également courants. Prenons par exemple rpc et idl, qui permettent à des programmes ou à des objets de communiquer au-delà des limites d’un processus ou d’une machine. Ou encore la grande variété de générateurs de scanners et d’analyseurs syntaxiques, dont lex et yacc sont les plus connus. Ils prennent en entrée une spécification grammaticale et génèrent du code qui implémente une machine à états. Les alternatives aux générateurs de code sont les compilateurs bricolés, les langages propriétaires ou les outils de programmation graphique dotés de boîtes de dialogue à sens unique ou d’assistants qui génèrent du code obscur au moment de la conception plutôt qu’au moment de la compilation. Plutôt que de lier nos clients à un compilateur C++ propriétaire ou à un environnement de développement intégré particulier, nous leur permettons d’utiliser les outils de leur choix. Au lieu d’obliger les programmeurs à ajouter du code généré dans leurs dépôts de sources, nous les encourageons à intégrer nos outils à leur système de build : c’est plus propre, plus sûr et plus conforme à l’esprit UNIX.
Les interfaces graphiques sont dynamiques
Le C++ est un langage polyvalent normalisé, puissant et sophistiqué. C’est le seul langage utilisé dans un éventail aussi large de projets logiciels, couvrant tous les types d’applications, des systèmes d’exploitation complets aux serveurs de bases de données et aux applications graphiques haut de gamme, en passant par les applications de bureau courantes. L’une des clés du succès du C++ réside dans la conception évolutive de son langage, qui privilégie des performances maximales et une consommation minimale de mémoire tout en conservant la compatibilité ANSI C.
Malgré tous ces avantages, il existe certains inconvénients. Pour le C++, le modèle d’objets statique constitue un désavantage évident par rapport à l’approche de messagerie dynamique d’Objective C lorsqu’il s’agit de programmer des interfaces graphiques basées sur des composants. Ce qui convient à un serveur de base de données haut de gamme ou à un système d’exploitation n’est pas nécessairement le bon choix de conception pour une interface graphique. Avec « moc », nous avons transformé cet inconvénient en avantage et ajouté la flexibilité nécessaire pour relever le défi d’une programmation d’interfaces graphiques à la fois sûre et efficace.
Notre approche va bien au-delà de ce que permettent les modèles (templates). Par exemple, nous pouvons disposer de propriétés d’objet. Nous pouvons également surcharger les signaux et les slots, ce qui semble naturel lorsqu’on programme dans un langage où la surcharge est un concept clé. Nos signaux n’ajoutent aucun octet à la taille d’une instance de classe, ce qui signifie que nous pouvons ajouter de nouveaux signaux sans rompre la compatibilité binaire.
Un autre avantage est que nous pouvons explorer les signaux et les slots d’un objet lors de l’exécution. Nous pouvons établir des connexions à l’aide d’un appel par nom (call-by-name) sécurisé au niveau des types, sans avoir à connaître les types exacts des objets que nous connectons. Cela est impossible avec une solution basée sur des modèles. Ce type d’introspection à l’exécution ouvre de nouvelles possibilités, par exemple des interfaces graphiques générées et connectées à partir des fichiers XML d’interface utilisateur d’ Qt Widgets Designer.
Les performances d’appel ne font pas tout
L’implémentation des signaux et slots de Qt n’est pas aussi rapide qu’une solution basée sur des modèles. Alors que l’émission d’un signal coûte approximativement l’équivalent de quatre appels de fonction ordinaires avec des implémentations de modèles courantes, Qt nécessite un effort comparable à environ dix appels de fonction. Cela n’a rien de surprenant puisque le mécanisme Qt intègre un marshaller générique, l’introspection, la mise en file d’attente des appels entre différents threads et, enfin, la possibilité d’utiliser des scripts. Il ne repose pas sur une intégration excessive ni sur l’expansion du code, et il offre une sécurité d’exécution inégalée. Les itérateurs de Qt sont sûrs, contrairement à ceux des systèmes plus rapides basés sur des modèles. Même pendant le processus d’émission d’un signal vers plusieurs récepteurs, ces derniers peuvent être supprimés en toute sécurité sans que votre programme ne plante. Sans cette sécurité, votre application finirait par planter à cause d’une erreur de lecture ou d’écriture dans de la mémoire libérée, difficile à déboguer.
Néanmoins, une solution basée sur des modèles ne pourrait-elle pas améliorer les performances d’une application utilisant des signaux et des slots ? S’il est vrai que Qt ajoute une légère surcharge au coût d’appel d’un slot via un signal, ce coût ne représente qu’une petite partie du coût total d’un slot. Les tests de performance par rapport au système de signaux et de slots de Qt sont généralement effectués avec des slots vides. Dès que vous effectuez une opération utile dans vos slots, par exemple quelques opérations simples sur des chaînes de caractères, la surcharge liée à l’appel devient négligeable. Le système de Qt est tellement optimisé que toute opération nécessitant l’opérateur `new` ou `delete` (par exemple, des opérations sur des chaînes de caractères ou l’insertion/la suppression d’un élément dans un conteneur de modèle) est nettement plus coûteuse que l’émission d’un signal.
Remarque : si vous avez une connexion « signaux et slots » dans une boucle interne serrée d’une tâche critique en termes de performances et que vous identifiez cette connexion comme le goulot d’étranglement, envisagez d’utiliser le modèle standard d’interface d’écouteur plutôt que les signaux et slots. Dans ce genre de cas, vous n’avez probablement besoin que d’une connexion 1:1 de toute façon. Par exemple, si vous disposez d’un objet qui télécharge des données depuis le réseau, il est tout à fait judicieux d’utiliser un signal pour indiquer que les données demandées sont arrivées. Mais si vous devez envoyer chaque octet un par un à un consommateur, utilisez une interface d’écoute plutôt que les signaux et les slots.
Aucune limite
Comme nous disposions de l’ moc e pour les signaux et les slots, nous avons pu y ajouter d’autres fonctionnalités utiles qui n’auraient pas pu être réalisées avec des modèles. Parmi celles-ci figurent les traductions avec portée via une fonction générée « tr() », ainsi qu’un système de propriétés avancé avec introspection et informations de type étendues à l’exécution. Le système de propriétés constitue à lui seul un avantage considérable : un outil de conception d’interface utilisateur puissant et générique tel qu’ Qt Widgets Designer serait bien plus difficile à écrire — voire impossible — sans un système de propriétés puissant et introspectif. Mais cela ne s’arrête pas là. Nous fournissons également un mécanisme d’ qobject_cast e dynamique <T>() qui ne repose pas sur le RTTI du système et n’en partage donc pas les limites. Nous l’utilisons pour interroger en toute sécurité les interfaces de composants chargés dynamiquement. Les méta-objets dynamiques constituent un autre domaine d’application. Nous pouvons par exemple prendre des composants ActiveX et créer, lors de l’exécution, un méta-objet autour de ceux-ci. Ou bien nous pouvons exporter des composants Qt en tant que composants ActiveX en exportant leur méta-objet. Aucune de ces opérations n’est possible avec les modèles.
Le C++ associé à l’ moc nous offre essentiellement la flexibilité d’Objective-C ou d’un environnement d’exécution Java, tout en conservant les avantages uniques du C++ en termes de performances et d’évolutivité. C’est ce qui fait de Qt l’outil flexible et convivial dont nous disposons aujourd’hui.
© 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.