Qu'est-ce que la réflexion et l'introspection en C++ et pourquoi en avez-vous besoin ?
\n\nImaginez que vous écrivez un moteur de jeu complexe ou un framework de traitement de données. À un moment donné, vous devez comprendre : « Qu'est-ce que cet objet exactement ? Quelles sont ses méthodes ? A-t-il un champ name ? »
Dans des langages comme Python ou Java, cela se fait en une seule ligne — ils disposent d'une réflexion complète (la capacité d'un programme à étudier et modifier sa propre structure pendant l'exécution). En C++, c'est plus compliqué. C++ est un langage à typage statique fort, et la réflexion « prête à l'emploi » est quasiment absente. Mais il existe l'introspection — une capacité limitée à obtenir des informations sur les types pendant l'exécution.
\n\nDans cet article, nous aborderons :
\n- \n
- La différence entre réflexion et introspection ; \n
- Comment fonctionne le RTTI (Run-Time Type Information) ; \n
- Comment utiliser
typeidetdynamic_cast; \n - Comment émuler la réflexion avec des templates et des macros ; \n
- Les alternatives modernes (C++20/23) et les bibliothèques. \n
Prêts à plonger dans le « miroir » du C++ ? C'est parti !
\n\nIntrospection vs Réflexion : quelle est la différence ?
\n\nAvant d'écrire du code, mettons les points sur les i.
\n\n- \n
- Introspection — c'est la capacité d'un programme à « se regarder lui-même » et à obtenir des informations sur les types, mais sans modifier la structure du programme. Par exemple : « Connaître le nom de la classe d'un objet ». Cela existe en C++ via le RTTI. \n
- Réflexion — c'est l'introspection + la capacité de créer dynamiquement des objets, d'appeler des méthodes, de modifier des champs. En C++, cela n'existe pas dans la norme (pour l'instant). \n
En C++, nous utilisons le terme « réflexion » plus souvent comme un objectif, et « introspection » comme un outil disponible. Mais ne vous inquiétez pas : même avec des moyens limités, on peut faire des choses impressionnantes.
\n\nRTTI (Run-Time Type Information) — le fondement de l'introspection en C++
\n\nLe RTTI est un mécanisme qui permet de déterminer le type d'un objet pendant l'exécution. Il est activé par défaut (bien que dans certains systèmes embarqués, on le désactive avec le flag -fno-rtti).
Les principaux acteurs du RTTI :
\n- \n
typeid— un opérateur qui retournestd::type_info; \n dynamic_cast— un cast sécurisé pour les types polymorphes. \n
Exemple 1 : Utilisation de typeid
\n\n#include <iostream>\n#include <typeinfo>\n\nclass Base { virtual void foo() {} };\nclass Derived : public Base {};\n\nint main() {\n Base* b = new Derived();\n std::cout << "Type de l'objet : " << typeid(*b).name() << std::endl;\n delete b;\n return 0;\n}\n\nSortie (dépend du compilateur) : class Derived ou 7Derived (nom déformé).
Notez que typeid ne fonctionne qu'avec les classes polymorphes (ayant au moins une méthode virtuelle). Si vous enlevez virtual, vous obtiendrez le type du pointeur, et non l'objet réel.
Exemple 2 : dynamic_cast — cast sécurisé
\n\n#include <iostream>\n\nclass Animal { public: virtual ~Animal() = default; };\nclass Dog : public Animal { public: void bark() { std::cout << "Woof!\\n"; } };\nclass Cat : public Animal { public: void meow() { std::cout << "Meow!\\n"; } };\n\nvoid makeSound(Animal* a) {\n if (Dog* dog = dynamic_cast<Dog*>(a)) {\n dog->bark();\n } else if (Cat* cat = dynamic_cast<Cat*>(a)) {\n cat->meow();\n } else {\n std::cout << "Animal inconnu\\n";\n }\n}\n\nint main() {\n Animal* pets[] = { new Dog(), new Cat(), new Animal() };\n for (auto p : pets) makeSound(p);\n // N'oublions pas de supprimer !\n for (auto p : pets) delete p;\n return 0;\n}\n\nC'est un exemple classique d'introspection : nous ne savons pas quel objet arrive, mais m