Was sind Reflexion und Introspection in C++ und wozu braucht man sie?
\n\nStellen Sie sich vor, Sie schreiben eine komplexe Spiel-Engine oder ein Framework zur Datenverarbeitung. Irgendwann müssen Sie verstehen: „Was ist das überhaupt für ein Objekt? Welche Methoden hat es? Hat es ein Feld name?“
In Sprachen wie Python oder Java erledigt man das mit einer Zeile – dort gibt es vollwertige Reflexion (die Fähigkeit eines Programms, seine eigene Struktur zur Laufzeit zu untersuchen und zu verändern). In C++ ist das jedoch komplizierter. C++ ist eine stark statisch typisierte Sprache und bietet „von Haus aus“ kaum Reflexion. Es gibt jedoch Introspection – die eingeschränkte Fähigkeit, Informationen über Typen zur Laufzeit zu erhalten.
\n\nIn diesem Artikel behandeln wir:
\n- \n
- Worin sich Reflexion und Introspection unterscheiden; \n
- Wie RTTI (Run-Time Type Information) funktioniert; \n
- Wie man
typeidunddynamic_castverwendet; \n - Wie man Reflexion mit Templates und Makros emuliert; \n
- Moderne Alternativen (C++20/23) und Bibliotheken. \n
Bereit für einen Blick in den „Spiegelsaal“ von C++? Los geht's!
\n\nIntrospection vs. Reflexion: Was ist der Unterschied?
\n\nBevor wir Code schreiben, lassen Sie uns die Begriffe klarstellen.
\n\n- \n
- Introspection – die Fähigkeit eines Programms, „sich selbst zu betrachten“ und Informationen über Typen zu erhalten, aber nicht die Programmstruktur zu verändern. Beispiel: „Den Klassennamen eines Objekts herausfinden“. Das ist in C++ über RTTI möglich. \n
- Reflexion – das ist Introspection plus die Fähigkeit, Objekte dynamisch zu erstellen, Methoden aufzurufen und Felder zu verändern. Das gibt es im C++-Standard (noch) nicht. \n
In C++ verwenden wir den Begriff „Reflexion“ oft als Ziel und „Introspection“ als das verfügbare Werkzeug. Aber keine Sorge: Auch mit den eingeschränkten Mitteln kann man tolle Dinge tun.
\n\nRTTI (Run-Time Type Information) – die Grundlage der Introspection in C++
\n\nRTTI ist ein Mechanismus, der es erlaubt, den Typ eines Objekts zur Laufzeit zu bestimmen. Er ist standardmäßig aktiviert (obwohl er in einigen eingebetteten Systemen mit dem Flag -fno-rtti deaktiviert wird).
Die Hauptakteure von RTTI:
\n- \n
typeid– ein Operator, derstd::type_infozurückgibt; \n dynamic_cast– eine sichere Typumwandlung für polymorphe Typen. \n
Beispiel 1: Verwendung von 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 << "Typ des Objekts: " << typeid(*b).name() << std::endl;\n delete b;\n return 0;\n}\n\nAusgabe (abhängig vom Compiler): class Derived oder 7Derived (gemangelter Name).
Beachten Sie: typeid funktioniert nur mit polymorphen Klassen (die mindestens eine virtuelle Methode haben). Wenn Sie virtual entfernen, erhalten Sie den Typ des Zeigers, nicht das tatsächliche Objekt.
Beispiel 2: dynamic_cast – sichere Typumwandlung
\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 << "Unbekanntes Tier\\n";\n }\n}\n\nint main() {\n Animal* pets[] = { new Dog(), new Cat(), new Animal() };\n for (auto p : pets) makeSound(p);\n // Nicht vergessen zu löschen!\n for (auto p : pets) delete p;\n return 0;\n}\n\nDies ist ein klassisches Beispiel für Introspection: Wir wissen nicht, welches konkrete Objekt ankommt, aber m