Sur cette page

Pourquoi Qt utilise-t-il Moc pour les signaux et les slots ?

Les modèles 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 transmis. En tant que tels, les modèles sont très intéressants pour les créateurs de frameworks, et nous utilisons des modèles avancés à de nombreux endroits dans Qt. Cependant, il y a des limitations : Il y a des choses que vous pouvez facilement exprimer avec des modèles, et il y a des choses qu'il est impossible d'exprimer avec des modèles. Une classe de conteneur vectoriel générique est facilement exprimable, même avec une spécialisation partielle pour les types de pointeurs, alors qu'une fonction qui met en place une interface utilisateur graphique basée sur une description XML donnée sous forme de chaîne n'est pas exprimable sous forme de modèle. Et puis il y a une zone grise entre les deux. Les choses que vous pouvez pirater avec des modèles au prix de la taille du code, de la lisibilité, de la portabilité, de la facilité d'utilisation, de l'extensibilité, de la robustesse et finalement de la beauté de la conception. Les modèles et le préprocesseur C peuvent être étendus pour faire des choses incroyablement intelligentes et époustouflantes. Mais ce n'est pas parce que ces choses peuvent être faites que c'est nécessairement le bon choix de conception. Le code, malheureusement, n'est pas destiné à être publié dans des livres, mais à être compilé avec des compilateurs du monde réel sur des systèmes d'exploitation du monde réel.

Voici quelques raisons pour lesquelles Qt utilise le moc :

L'importance de la syntaxe

La syntaxe n'est pas que du sucre : la syntaxe que nous utilisons pour exprimer nos algorithmes peut affecter de manière significative 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. La syntaxe est intuitive, simple à utiliser et facile à lire. Les personnes qui apprennent Qt Help trouvent que la syntaxe les aide à comprendre et à utiliser le concept des signaux et des slots - malgré sa nature très abstraite et générique. Cela permet aux programmeurs d'obtenir une conception correcte dès le début, sans même avoir à penser aux modèles de conception.

Les générateurs de code sont bons

Le Meta-Object Compiler ( moc ) de Qt fournit un moyen propre d'aller au-delà des facilités du langage compilé. Il génère du code C++ supplémentaire qui peut être compilé par n'importe quel compilateur C++ standard. Le moc lit les fichiers source C++. S'il trouve une ou plusieurs déclarations de classes contenant la macro Q_OBJECT, il produit un autre fichier source C++ contenant le méta-code objet de ces classes. Le fichier source C++ généré par moc doit être compilé et lié à l'implémentation de la classe (ou il peut être #included dans le fichier source de la classe). Généralement, moc n'est pas appelé manuellement, mais automatiquement par le système de compilation, de sorte qu'il ne nécessite aucun effort supplémentaire de la part du programmeur.

Le moc n'est pas le seul générateur de code utilisé par Qt XML. Un autre exemple important est le uic (User Interface Compiler). Il prend une description de l'interface utilisateur en XML et crée un code C++ qui met en place le formulaire. En dehors de Qt, les générateurs de code sont également courants. Prenons par exemple rpc et idl, qui permettent aux programmes ou aux objets de communiquer au-delà des limites du processus ou de la machine. Ou encore la grande variété de générateurs de scanners et d'analyseurs, dont lex et yacc sont les plus connus. Ils prennent en entrée une spécification de grammaire et génèrent un code qui met en œuvre une machine à états. Les alternatives aux générateurs de code sont des compilateurs piratés, des langages propriétaires ou des outils de programmation graphique avec des dialogues à sens unique ou des assistants qui génèrent un code obscur au moment de la conception plutôt qu'au moment de la compilation. Plutôt que d'enfermer nos clients dans un compilateur C++ propriétaire ou dans un environnement de développement intégré particulier, nous leur permettons d'utiliser les outils qu'ils préfèrent. Au lieu de forcer les programmeurs à ajouter le code généré dans les dépôts de sources, nous les encourageons à ajouter nos outils à leur système de construction : plus propre, plus sûr et plus conforme à l'esprit d'UNIX.

Les interfaces graphiques sont dynamiques

Le C++ est un langage standardisé, puissant et élaboré à usage général. C'est le seul langage qui soit exploité dans une gamme aussi large de projets logiciels, couvrant tous les types d'applications, depuis les systèmes d'exploitation complets, les serveurs de base de données et les applications graphiques haut de gamme jusqu'aux applications de bureau courantes. L'une des clés du succès du C++ est la conception évolutive du langage, qui se concentre sur des performances maximales et une consommation de mémoire minimale, tout en conservant la compatibilité avec le C ANSI.

Tous ces avantages ne sont pas sans inconvénients. Pour le C++, le modèle d'objet statique est un désavantage évident par rapport à l'approche de messagerie dynamique de l'Objective C lorsqu'il s'agit de programmation d'interface utilisateur graphique basée sur des composants. Ce qui est bon pour 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 frontale. Avec moc, nous avons transformé cet inconvénient en avantage et ajouté la flexibilité nécessaire pour relever le défi d'une programmation sûre et efficace de l'interface utilisateur graphique.

Notre approche va bien au-delà de ce que l'on peut faire avec des modèles. Par exemple, nous pouvons avoir des propriétés d'objet. Nous pouvons également avoir des signaux surchargés et des slots, ce qui semble naturel lorsque l'on programme dans un langage où les surcharges sont un concept clé. Nos signaux ajoutent zéro 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 au moment de l'exécution. Nous pouvons établir des connexions à l'aide d'un appel par nom sécurisé, 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 au moment de 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 de Qt Widgets Designer.

La performance des appels n'est pas tout

L'implémentation des signaux et des slots de Qt n'est pas aussi rapide qu'une solution basée sur un modèle. Alors que l'émission d'un signal correspond approximativement au coût de quatre appels de fonctions ordinaires avec les implémentations de modèles courantes, Qt requiert un effort comparable à environ dix appels de fonctions. Cela n'est pas surprenant puisque le mécanisme de Qt comprend un marshaller générique, une introspection, des appels en file d'attente entre différents threads et, en fin de compte, la possibilité de créer des scripts. Il ne repose pas sur un inlining et une expansion du code excessifs et offre une sécurité d'exécution inégalée. Les itérateurs de Qt sont sûrs alors que ceux des systèmes plus rapides basés sur des modèles ne le sont pas. 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 s'arrête. Sans cette sécurité, votre application finirait par planter avec une erreur de lecture ou d'écriture de mémoire libre 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 petite surcharge au coût d'appel d'un slot par le biais d'un signal, le coût de l'appel ne représente qu'une petite partie du coût total d'un slot. L'évaluation comparative du système de signaux et de slots de Qt se fait généralement avec des slots vides. Dès que vous faites quelque chose d'utile dans vos slots, par exemple quelques opérations simples sur des chaînes de caractères, le coût d'appel devient négligeable. Le système de Qt est tellement optimisé que tout ce qui nécessite un opérateur new ou delete (par exemple, les opérations sur les chaînes de caractères ou l'insertion/suppression de quelque chose dans un conteneur de modèle) est significativement plus coûteux que l'émission d'un signal.

Remarque : si vous avez une connexion de signaux et de 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, pensez à utiliser le modèle standard de l'interface d'écoute plutôt que les signaux et les slots. Dans les cas où cela se produit, vous n'avez probablement besoin que d'une connexion 1:1 de toute façon. Par exemple, si vous avez un objet qui télécharge des données à partir du réseau, il est tout à fait raisonnable 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 des signaux et des slots.

Pas de limites

Puisque nous disposions de moc pour les signaux et les slots, nous pouvions y ajouter d'autres choses utiles qui ne pouvaient pas être réalisées avec des templates. Parmi celles-ci, on peut citer les traductions cadrées via une fonction tr() générée, et un système de propriétés avancé avec introspection et informations de type étendues au moment de l'exécution. Le système de propriétés est à lui seul un grand avantage : un outil de conception d'interface utilisateur puissant et générique comme Qt Widgets Designer serait beaucoup plus difficile à écrire - voire impossible - sans un système de propriétés puissant et introspectif. Mais ce n'est pas tout. Nous fournissons également un mécanisme dynamique qobject_cast<T>() qui ne repose pas sur le RTTI du système et ne partage donc pas ses limitations. Nous l'utilisons pour interroger en toute sécurité les interfaces des 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 un méta-objet autour d'eux au moment de l'exécution. Nous pouvons également exporter des composants Qt en tant que composants ActiveX en exportant leur objet méta. Il est impossible de faire l'une ou l'autre de ces choses avec des modèles.

C++ avec moc nous donne essentiellement la flexibilité d'Objective-C ou d'un environnement d'exécution Java, tout en conservant les avantages uniques de performance et d'évolutivité de C++. C'est ce qui fait de Qt l'outil flexible et confortable que nous avons 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.