Tu amigo RTTI (comprobando tipos en C++)

Uno de esos vicios heredados de programar en C, es que te lo tienes que montar todo por tu propia cuenta, el lenguaje te da lo mínimo para poder funcionar. Es una situación que para aprender a programar está bien, ya que C es un lenguaje lo suficientemente sencillo como para enseñar la base de la programación. Pero no es especialmente productivo, ya que por ejemplo para hacer un array genérico de información hay que recurrir a cosas como la siguiente:

typedef struct _GenericData {
    int type;
    void * data;
} GenericData;

No es el fin del mundo, ni mucho menos, pero luego llega el típico include con una lista de #define para recoger todos los posibles tipos de información, reunidos en un solo fichero para evitar equivocarnos y poner como identificador de tipo a dos estructuras distintas el mismo entero. El problema de esto, es que al añadir un nuevo identificador, a recompilar todos los .c que lo incluían. La vida así no es especialmente cómoda, la verdad. Y no es especialmente natural esta forma de trabajar.

#include <iostream>
#include <string>
using namespace std;

class Father {
public:
    Father() {}
    virtual ~Father() {}
    virtual void ShowMsg() {
        cout << "Father wins..." << endl;
    }
};

class Child : public Father {
public:
    Child() {}
    virtual ~Child() {}
    virtual void ShowMsg() {
        cout << "Child wins..." << endl;
    }
    void ShowExtraMsg() {
        cout << "Child extra wins..." << endl;
    }
};

Así que ahora estamos en C++, recién llegados de C y tras leer libros, tutoriales y manuales, hemos más o menos comprendido de qué va eso de la encapsulación, la herencia y el polimorfismo. Tras un buen rato alucinando y maldiciendo, afirmando incluso que esto es una locura y que en C todo es mucho más sencillo, nos acabamos acostumbrando a la forma de expresarnos en C++. Pero llegamos al punto de hacer una lista con punteros a la clase padre, para poder contener todo tipo de hijos… y tras un buen rato, uno frunce el ceño y se pregunta: “¿Y ahora como coño voy a saber de qué tipo es cada objeto?”

La primera idea que viene a la cabeza es meter en el padre un entero para poder indicar el tipo del objeto, que es exactamente lo mismo que se haría en C. ¿El problema? Pues el mismo que C o mayor, porque el número de .cpp que tendrían incluida la cabecera con la lista de constantes de identificadores de tipo sería mayor, al final perderíamos la cordura de un modo u otro. Entonces sumido en la desesperación, es posible que uno recuerde de haber leído algo sobre un operador parecido a sizeof, pero que se llama typeid, algo que leímos y pensamos en su momento: “Bah, esto seguro que no sirve para nada…”

Father father;
Child child;

string fatherTypeName = "(father: " +
       string(typeid(father).name()) + ")";
string childTypeName = "(child: " +
       string(typeid(child).name()) + ")";
string classFatherTypeName = "(" +
       string(typeid(Father).name()) + ")";

bool fstEq = typeid(father) == typeid(child);
bool sndEq = typeid(father) == typeid(Father);

cout << fatherTypeName << " == " << childTypeName
     << " -> " << fstEq << endl;
cout << fatherTypeName << " == " << classFatherTypeName
     << " -> " << sndEq << endl;

Como se puede ver en este ejemplo al final sí que valía para algo. Por supuesto para poder darle uso a este operador tiene que estar activado el RTTI, que lo único que hará es que el código ocupe un poco más para almacenar la información relativa a la descripción del tipo. Así de forma sencilla podemos comprobar con una mera igualdad si dos objetos son de la misma clase, o si un objeto es de un tipo determinado o no. Incluso podemos hacer más cosas como obtener una cadena con el nombre identificador del tipo, que para temas del log y de depuración podría ser interesante.

Father * ptr = &child;
cout << "typeid information" << endl;
cout << " Name:   " << typeid(*ptr).name() << endl;
cout << "         " << (long long)typeid(*ptr).name() << endl;
cout << "         " << (long int)typeid(*ptr).name() << endl;
cout << " Raw:    " << typeid(*ptr).raw_name() << endl;
cout << " Before: " << typeid(*ptr).before(typeid(father)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(child)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(char)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(int)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(float)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(string)) << endl;
cout << " Before: " << typeid(*ptr).before(typeid(Father)) << endl;
cout << " Hash:   " << typeid(*ptr).hash_code() << endl;

Lo primero que hay que entender es que al aplicar el operador typeid obtendremos un objeto de tipo type_info, que es un tipo de clase que no podemos instanciar, ya que de poder hacerlo alguno se dedicaría a liarla parda. Esta clase nos da algunos métodos y sobrecarga el operador == y el != para poder comprobar la igualdad entre tipos. Sus métodos son:

  • const char * name() const; → Nos da el nombre completo del tipo, con los namespace incluidos.
  • const char * raw_name() const; → Nos da el nombre del tipo sin traducir al lenguaje.
  • size_t hash_code() const; → Devuelve el valor hash del tipo.
  • int before(const type_info & rhs) const; → Este método es raro y así de primeras da la sensación intuitiva de servir para comprobar si un tipo hereda de otro, pero es una asunción equivocada. En teoría serviría para saber si un type_info está antes que otro en la memoria, una información que no alcanzo a imaginar para qué podría ser útil, pero ahí está ^_^U

Como se puede ver algunos métodos son un poco esotéricos y tal, y la mayoría de las veces con obtener el descriptor del tipo y de ahí su nombre tendremos más que suficiente para trabajar, pero nunca está de más poder saber un poquito más por si acaso… nunca se sabe cuando la mierda te va a llegar hasta el cuello, jej…

Si queréis leer más, y sabéis inglés (algo inexorablemente necesario), en la MSDN tenéis una entrada sobre typeid y type_info, que siempre se pierde algo con la traducción de la información o algo así xDDD

Advertisement

Deja un comentario

Please log in using one of these methods to post your comment:

Logo de WordPress.com

You are commenting using your WordPress.com account. Log Out / Cambiar )

Twitter picture

You are commenting using your Twitter account. Log Out / Cambiar )

Facebook photo

You are commenting using your Facebook account. Log Out / Cambiar )

Connecting to %s

Seguir

Get every new post delivered to your Inbox.