03.11
2020
Em C++ é possível avaliar o tipo em tempo de execução, o que pode ser muito útil quando estamos falando de abstração e derivação de classes. Com a função type_id e classe type_info é possível obter informações sobre um tipo, classe, variável ou objetos.
Vamos começar com a função type_id(), uma função que recupera informações de tipo de um tipo, variável, classe ou objeto. A função retorna um objeto do tipo std::type_info, que encapsula algumas informações sobre o tipo da entidade.
A class std::type_info entrega os operadores == e !=, de igualdade ou diferença, um método hash_code(), que retorna um valor numérico que representa o objeto, e um método name(), que retorna um const char* com uma representação textual do tipo em questão.
Tipos Básicos
Vamos começar com os tipos básicos:
#include <iostream>
#include <typeinfo>
int main() {
char c = '\x0';
int i = 0;
std::cout << "typeid(c).name(): " << typeid(c).name() << std::endl;
std::cout << "typeid(i).name(): " << typeid(i).name() << std::endl;
std::cout << "typeid(&i).name(): " << typeid(&i).name() << std::endl;
std::cout << "typeid(double).name(): " << typeid(double).name() << std::endl;
}
Que reproduz a seguinte saída:
typeid(c).name(): c
typeid(i).name(): i
typeid(&i).name(): Pi
typeid(double).name(): d
Em alguns compiladores o método std::type_info::name() retorna o nome do tipo, em outros somente um código, como no caso do GCC, utilizado para gerar a saída do exemplo. A tabela completa dos códigos dos tipos pode ser onferida neste link. São ao todo 32 tipos básicos diferentes suportados pelo type_info.
Classes e Objetos
Agora um exemplo com classes e objetos:
#include <iostream>
#include <typeinfo>
class Parent {
public:
Parent() { }
virtual void m() {
}
};
class Child1 : public Parent {
public:
Child1() { }
virtual void m() { }
};
class Child2 : public Parent {
public:
Child2() { }
virtual void m() { }
};
int main() {
Parent parent;
Child1 child1;
Child2 child2;
std::cout << "typeid(parent).name(): " << typeid(parent).name() << std::endl;
std::cout << "typeid(child1).name(): " << typeid(child1).name() << std::endl;
std::cout << "typeid(&child2).name(): " << typeid(&child2).name() << std::endl;
Parent *p = &parent;
Parent *p1 = &child1;
Parent *p2 = &child2;
std::cout << "typeid(*p).name(): " << typeid(*p).name() << std::endl;
std::cout << "typeid(*p1).name(): " << typeid(*p1).name() << std::endl;
std::cout << "typeid(*p2).name(): " << typeid(*p2).name() << std::endl;
}
Que entregará a saída:
typeid(parent).name(): 6Parent
typeid(child1).name(): 6Child1
typeid(&child2).name(): P6Child2
typeid(*p).name(): 6Parent
typeid(*p1).name(): 6Child1
typeid(*p2).name(): 6Child2
Note que o ponteiro Parent, quando apontando para um classe Child1, ou Child2, e desreferenciado indica qual é o tipo do objeto, e não do ponteiro. Essa funcionalidade pode ser muito útil para identificar qual o tipo de fato apontado por um ponteiro:
#include <algorithm>
#include <iostream>
#include <list>
#include <typeinfo>
class Parent {
public:
virtual void vm() = 0;
};
class Child1 : public Parent {
public:
Child1() { }
void vm() { return; }
};
class Child2 : public Parent {
public:
Child2() { }
void vm() { return; }
};
int main() {
std::list<Parent*> lst;
lst.push_back(new Child1());
lst.push_back(new Child2());
lst.push_back(new Child1());
lst.push_back(new Child2());
std::for_each(lst.begin(), lst.end(), [](Parent* p) {
if (typeid(Child1) == typeid(*p))
std::cout << "Child1" << std::endl;
else if (typeid(Child2) == typeid(*p))
std::cout << "Child2" << std::endl;
else
std::cout << "Unknown" << std::endl;
delete p;
});
}
O código acima entregará a seguinte saída:
Child1
Child2
Child1
Child2
Identificando corretamente qual a classe de cada objeto adicionado no std::list.
Funções e Métodos
É possível também verificar o tipo de ponteiros para funções e métodos:
#include <iostream>
#include <typeinfo>
class Parent {
public:
Parent() { }
virtual void m() { }
};
class Child1 : public Parent {
public:
Child1() { }
virtual void m() { }
};
class Child2 : public Parent {
public:
Child2() { }
};
void f1(int x) { }
int main() {
Parent parent;
Child1 child1;
Child2 child2;
std::cout << "typeid(f1).name(): " << typeid(f1).name() << std::endl;
std::cout << "typeid(Parent::m).name(): " << typeid(Parent::m).name() << std::endl;
std::cout << "typeid(Child1::m).name(): " << typeid(Child1::m).name() << std::endl;
std::cout << "typeid(Child2::m).name(): " << typeid(Child2::m).name() << std::endl;
}
Que retornará a seguinte saída:
typeid(f).name(): FviE
typeid(Parent::m).name(): M6ParentFvvE
typeid(Child1::m).name(): M6Child1FvvE
typeid(Child2::m).name(): M6ParentFvvE
Identificando corretamente qual método irá de fato ser executado em cada caso, conforme a sobrecarga definida na classe do objeto.
Nota: as saídas exibidas neste post foram gerados pelo compilador GCC 8.1.0