¿Qué es la reflexión y la introspección en C++ y para qué sirven?
\n\nImagina que estás escribiendo un motor de juegos complejo o un framework para procesamiento de datos. En algún momento necesitas saber: «¿Qué es este objeto realmente? ¿Qué métodos tiene? ¿Tiene un campo name?»
En lenguajes como Python o Java, esto se hace en una sola línea; allí hay una reflexión completa (la capacidad de un programa para estudiar y modificar su propia estructura durante la ejecución). Pero en C++ es más complicado. C++ es un lenguaje con tipado estático fuerte, y «de fábrica» casi no hay reflexión. Sin embargo, existe la introspección — una capacidad limitada para obtener información sobre los tipos en tiempo de ejecución.
\n\nEn este artículo analizaremos:
\n- \n
- En qué se diferencia la reflexión de la introspección; \n
- Cómo funciona RTTI (Run-Time Type Information); \n
- Cómo usar
typeidydynamic_cast; \n - Cómo emular la reflexión con plantillas y macros; \n
- Alternativas modernas (C++20/23) y bibliotecas. \n
¿Listo para mirar en el «espejo» de C++? ¡Vamos allá!
\n\nIntrospección vs Reflexión: ¿cuál es la diferencia?
\n\nAntes de escribir código, pongamos los puntos sobre las íes.
\n\n- \n
- Introspección — es la capacidad de un programa de «mirarse a sí mismo» y obtener información sobre los tipos, pero sin modificar la estructura del programa. Por ejemplo: «Conocer el nombre de la clase de un objeto». Esto existe en C++ a través de RTTI. \n
- Reflexión — es introspección + la capacidad de crear objetos dinámicamente, invocar métodos, modificar campos. En C++ esto no está en el estándar (por ahora). \n
En C++ usamos el término «reflexión» más a menudo como un objetivo, y «introspección» como una herramienta disponible. Pero no te asustes: incluso con medios limitados se pueden hacer cosas geniales.
\n\nRTTI (Run-Time Type Information) — la base de la introspección en C++
\n\nRTTI es un mecanismo que permite determinar el tipo de un objeto durante la ejecución. Está activado por defecto (aunque en algunos sistemas embebidos se desactiva con la bandera -fno-rtti).
Los principales actores de RTTI:
\n- \n
typeid— operador que devuelvestd::type_info; \n dynamic_cast— conversión segura de tipos polimórficos. \n
Ejemplo 1: Uso 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 << "Tipo del objeto: " << typeid(*b).name() << std::endl;\n delete b;\n return 0;\n}\n\nSalida (depende del compilador): class Derived o 7Derived (nombre alterado).
Ten en cuenta: typeid solo funciona con clases polimórficas (que tengan al menos un método virtual). Si eliminas virtual, obtendrás el tipo del puntero, no el objeto real.
Ejemplo 2: dynamic_cast — conversión segura
\n\n#include <iostream>\n\nclass Animal { public: virtual ~Animal() = default; };\nclass Dog : public Animal { public: void bark() { std::cout << "¡Guau!\\n"; } };\nclass Cat : public Animal { public: void meow() { std::cout << "¡Miau!\\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 desconocido\\n";\n }\n}\n\nint main() {\n Animal* pets[] = { new Dog(), new Cat(), new Animal() };\n for (auto p : pets) makeSound(p);\n // ¡No olvides eliminar!\n for (auto p : pets) delete p;\n return 0;\n}\n\nEste es un ejemplo clásico de introspección: no sabemos qué objeto específico llegó, pero podemos