Descobrindo Tipos com typeinfo

por Fabio A. Mazzarino

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