O que é reflexão e introspecção em C++ e para que servem?
\n\nImagine que você está escrevendo um motor de jogo complexo ou um framework para processamento de dados. Em algum momento, você precisa entender: "O que é esse objeto, afinal? Quais métodos ele tem? Ele tem um campo name?"
Em linguagens como Python ou Java, isso é feito em uma linha — elas têm reflexão completa (a capacidade do programa de estudar e modificar sua própria estrutura durante a execução). Já em C++, é mais complicado. C++ é uma linguagem com tipagem estática forte, e "de fábrica" quase não há reflexão. Mas existe a introspecção — a capacidade limitada de obter informações sobre tipos em tempo de execução.
\n\nNeste artigo, vamos abordar:
\n- \n
- A diferença entre reflexão e introspecção; \n
- Como funciona o RTTI (Run-Time Type Information); \n
- Como usar
typeidedynamic_cast; \n - Como emular reflexão com templates e macros; \n
- Alternativas modernas (C++20/23) e bibliotecas. \n
Pronto para mergulhar no "espelho" do C++? Vamos lá!
\n\nIntrospecção vs Reflexão: qual a diferença?
\n\nAntes de escrever código, vamos esclarecer os pontos.
\n\n- \n
- Introspecção — é a capacidade do programa de "olhar para si mesmo" e obter informações sobre tipos, mas sem modificar a estrutura do programa. Exemplo: "Descobrir o nome da classe de um objeto". Isso existe em C++ através do RTTI. \n
- Reflexão — é a introspecção + a capacidade de criar objetos dinamicamente, chamar métodos, modificar campos. Isso não existe no padrão C++ (ainda). \n
Em C++, usamos o termo "reflexão" mais como um objetivo, e "introspecção" como a ferramenta disponível. Mas não se assuste: mesmo com meios limitados, é possível fazer coisas incríveis.
\n\nRTTI (Run-Time Type Information) — a base da introspecção em C++
\n\nRTTI é um mecanismo que permite determinar o tipo de um objeto durante a execução. Ele é ativado por padrão (embora em alguns sistemas embarcados seja desativado com a flag -fno-rtti).
Os principais componentes do RTTI:
\n- \n
typeid— operador que retornastd::type_info; \n dynamic_cast— conversão segura de tipos polimórficos. \n
Exemplo 1: Usando 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 do objeto: " << typeid(*b).name() << std::endl;\n delete b;\n return 0;\n}\n\nSaída (depende do compilador): class Derived ou 7Derived (nome distorcido).
Observe: typeid funciona apenas com classes polimórficas (que tenham pelo menos um método virtual). Se remover o virtual, você obterá o tipo do ponteiro, não o objeto real.
Exemplo 2: dynamic_cast — conversão segura
\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 desconhecido\\n";\n }\n}\n\nint main() {\n Animal* pets[] = { new Dog(), new Cat(), new Animal() };\n for (auto p : pets) makeSound(p);\n // Não esquecer de deletar!\n for (auto p : pets) delete p;\n return 0;\n}\n\nEste é um exemplo clássico de introspecção: não sabemos qual objeto chegou, mas m