Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.9.14  Operador typeid

§1  Sinopsis

La palabra clave typeid identifica un operador con el que puede obtenerse el tipo de objetos y expresiones en tiempo de ejecución. Permite comprobar si un objeto es de un tipo particular, y si dos objetos son del mismo tipo. Este tipo de identificación en tiempo de ejecución es conocida abreviadamente como RTTI .

§2  Sintaxis

typeid( expresion )
typeid( nombre-de-tipo )

§3  Ejemplo

cout << typeid(int).name() << endl;
int x = 10, z = 20;
cout << typeid(x).name() << endl;
const type_info & ref = typeid(int);
cout << ref.name() << endl;

§4  Descripción

Como puede verse, el operador typeid acepta dos variantes sintácticas que adoptan la forma de función. En la primera, el operando es una expresión que adopta el papel de argumento de la función; una llamada a esta función devuelve una referencia ( 4.2.3) a un objeto de tipo constante, type_info. Como veremos a continuación , este objeto describe el tipo del operando [1]. Ejemplo:

int x = 10, y = 20;
const type_info & tipo1 = typeid(x+z);
class C { int x; } c1;
const type_info & tipo2 = typeid(c1);


La segunda forma sintáctica permite que el operando sea un nombre-de-tipo. Entonces typeid devuelve la referencia a un objeto type_info para ese tipo. El operador puede utilizarse tanto con tipos fundamentales como con tipos definidos por el usuario. Ejemplo:

const type_info & tipo3 = typeid(float);
const type_info & tipo4 = typeid(C);


Generalmente esta función-operador se utiliza para obtener el tipo de objetos referenciados mediante punteros o referencias. Situaciones como:

tipoX  Objeto;
tipoX* puntero = &Objeto;
tipoX& referencia = Objeto;
....
cout << "El objeto es de tipo: " << typeid(*puntero).name();
cout << "El objeto es de tipo: " << typeid(referencia).name();

Observe que la expresión  typeid(puntero) devuelve tipoX*

Nota: la salida no está normalizada, de forma que depende del compilador ( Nota-1)

El operador typeid no puede ser sobrecargado ( 4.9.18).

§5  El objeto type_info

Como se ha dicho, typeid devuelve una referencia a un objeto de tipo constante que describe el tipo del operando. Este objeto es la instancia de una clase denominada type_info que dispone de los operadores == y != que pueden utilizarse para comprobar si dos objetos son del mismo tipo. Esto significa que cuando utilizamos una expresión como:

if (typeid( X ) == typeid( Z ))

el operador == es una versión específica (sobrecargada) del operador igualdad para la clase type_info. Esta clase también dispone de sendos métodos: name() y before() a disposición del usuario. Recuerde que para utilizar la clase type_info es necesario incluir el fichero de cabecera <typeinfo>.

Lo anterior puede resumirse diciendo que el resultado del operador es la referencia a un objeto de una clase, y que este objeto solo puede ser utilizado mediante dos métodos y dos operadores para comparación. Para más información al respecto ver: 4.9.14a.

  Cuando el operador typeid se intenta aplicar a un operando que es la deferencia ( 4.9.11) de un puntero nulo ( 4.2.1), se lanza la excepción ( 1.6) bad_typeid (ver advertencias y notas que siguen).

Es importante señalar que aunque puede usarse con cualquier objeto, este operador se ha pensado para obtener el tipo de objetos polimórficos. Por supuesto no tiene mucho sentido una expresión como:

cout << "El tipo int es de tipo: " << typeid(int).name();


A pesar de lo que señalan la mayoría de los manuales y la bibliografía, typeid solo calcula en tiempo de ejecución el tipo de objetos polimórficos. En otro caso (si el objeto no es polimórfico) los calcula en tiempo de compilación [2]. Puede comprobarse con un sencillo ejemplo:

#include <iostream>
#include <typeinfo>
using namespace std;
 
int main() {       // ===========
  try {
     int* ptr = 0;
     const type_info & refx = typeid(*ptr);
     cout << "El tipo es: " << refx.name() << endl;
  }
  catch(...) {
     cout << "Recibida excepción." << endl;
     return 0;
  }
  cout << "NO Recibida excepción !!!!" << endl;
  return 0;
}


Contra lo señalado en el párrafo anterior sobre el lanzamiento de la excepción bad_typeid , el programa produce la misma salida con todos los compiladores en que he probado (Borland C++ 5.5;  MS Visual C++ V 6.0, y Cpp version 2.95.2 GNU/Linux):

El tipo es: int
NO Recibida excepción !!!!

La razón es la ya apuntada: typeid solo calcula en tiempo de ejecución el tipo de objetos polimórficos (que tienen al menos un método virtual 4.11.8). En este caso, en que el tipo de *ptr es siempre int, no se lanza la excepción a pesar de que se trata del valor de un puntero nulo.


§5.1  Una versión del programa anterior que pusiera de manifiesto el lanzamiento de la excepción bad_typeid, sería el siguiente:

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

  struct X {
    virtual void f() =0;
  };
  struct Y : X { void f() { } };

int main() {       // ===========
  try {
    X* ptr = 0;
    const type_info& refx = typeid(*ptr);
    cout << "El tipo es: " << refx.name() << endl;
  }
  catch(...) {
    cout << "Recibida excepción." << endl;
    return 0;
  }
  cout << "NO Recibida excepción !!!!" << endl;
  return 0;
}

En este caso la salida es:

Recibida excepción.


§5.2  El ejemplo siguiente muestra el uso del operador typeid, y de los dos métodos before() y name() de la clase type_info.

#include <iostream.h>
#include <typeinfo.h>

class A { };
class B : A { };

void main() {    // ==========
   char C;
   float X;

   // Uso de type_info::operator==() para hacer la comparación
   if (typeid( C ) == typeid( X ))
      cout << "C y X son del mismo tipo." << endl;
   else
      cout << "C y X son de distinto tipo." << endl;

   // Uso de literales true y false para hacer la comparación
   cout << typeid(int).name();
   cout << " antes que " << typeid(double).name() << ": " <<
        (typeid(int).before(typeid(double)) ? true : false) << endl;
   cout << typeid(double).name();
   cout << " antes que " << typeid(int).name() << ": " <<
        (typeid(double).before(typeid(int)) ? true : false) << endl;
   cout << typeid(A).name();
   cout << " antes " << typeid(B).name() << ": " <<
        (typeid(A).before(typeid(B)) ? true : false) << endl;
}

Salida:

C y X son de distinto tipo.
int antes que double: 0
double antes que int: 1
A antes que B: 1


§5.3  El siguiente ejemplo muestra la utilización de typeid para ilustrar una fuente de posibles errores en la declaración de punteros:

#include <iostream.h>
#include <typeinfo.h>

void main() {           // ======================
  int* pt1, pt2;        // L.5:  Ojo posible error !!
  if (typeid( pt1 ) == typeid( pt2 ))
    cout << "pt1 y pt2 son del mismo tipo." << endl;
  else
    cout << "pt1 y pt2 son de distinto tipo." << endl;

  int * pt3; int* pt4;  // L.11: Ok. más seguro
  if (typeid( pt3 ) == typeid( pt4 ))
    cout << "pt3 y pt4 son del mismo tipo." << endl;
  else
    cout << "pt3 y pt4 son de distinto tipo." << endl;
}

Salida:

pt1 y pt2 son de distinto tipo.
pt3 y pt4 son del mismo tipo.

Nota: el resultado obtenido con expresiones del tipo de L.5 depende de la implementación, pero ha sido idéntico con los compiladores Borland C++  5.5 y MS Visual C++ 6.0, por lo que en la declaración de punteros son preferibles expresiones como L.11.


§5.4
  A continuación se muestra otro ejemplo en el que el operador typeid es utilizado para seleccionar el constructor adecuado a un objeto. Suponemos que el tipo de objeto a manejar se presenta como una cadena alfanumérica obtenida de algún modo. Por ejemplo, la consulta a una base de datos, introducida por teclado o leída de un fichero, etc.

void HandleType(char* typeName) {
  if (strcmp(typeName, typeid(Clase1).name())==0) {
    Clase1 obj;
    obj.display();
  }
  else if (strcmp(typeName, typeid(Clase2).name())==0) {
    Clase2 obj;
    obj.display();
  }
  else if (strcmp(typeName, typeid(Clase3).name())==0) {
    Clase3 obj;
    obj.display();
  }
  ...      // etc.
}

§6  RTTI

El mecanismo C++ que permite determinar en tiempo de ejecución el tipo de un objeto, se conoce generalmente por su acrónimo inglés RTTI ("Run time type identification"). Este sistema es una parte importante del mecanismo de comprobación de tipos del lenguaje ( 2.2) y permite la identificación incluso cuando el objeto solo es accesible mediante un puntero o referencia [3]. Por ejemplo, el sistema hace posible convertir un puntero a clase-base virtual en puntero a clase derivada. Recordar que para realizar modelados en tiempo de ejecución debe utilizarse el operador dynamic_cast ( 4.9.9c).

Este mecanismo también permite comprobar si un objeto es de un tipo particular y cuando dos objetos son del mismo o distinto tipo. Esto puede hacerse con el operador typeid que da título al presente capítulo.

El mecanismo RTTI forma parte de un sistema más amplio de funciones y/o clases de la Librería Estándar C++ que proporcionan determinadas funcionalidades de tiempo de ejecución ( 1.5.2  Soporte de runtime).

§6.1  Deshabilitar el mecanismo RTTI

La utilización del mecanismo RTTI produce cierta sobrecarga en tiempo de ejecución, por lo que la mayoría de compiladores disponen de una opción para habilitarlo o deshabilitarlo a voluntad.  En concreto, C++Builder dispone de una opción de compilación ( 1.4.3), el comando -RT-, que provoca que no se genere código que permite la identificación de tipos en tiempo de ejecución (RTTI). Por defecto su estado es activado (ON). Por su parte, el compilador GNU gcc dispone de la opción -fno-rtti cuya finalidad es análoga.  Recordar también que esta opción puede ser incluida de forma particular en la declaración de clases ( 4.11.2).

Si se activa la opción de habilitar limpieza total de destructores para el manejo de excepciones (-xd 1.6), también se necesita activar esta opción. También es necesaria si se desea utilizar el operador dynamic_cast, que se basa en este mecanismo para comprobar si el modelado está permitido o no ( 4.9.9c).

  Inicio.


[1]  Esta observación es válida solamente para el compilador Borland C++: cuando el operando de typeid es una clase o referencia a un objeto Delphi, devuelve el tipo estático (de tiempo de compilación) en vez del de tiempo de ejecución. Ejemplo:

static const char *TypeIdName(TObject *c) {
   return typeid(*c).name();
}
//  The button’s caption is set to TObject, not TButton.
void __fastcall TForm1::Button1Click(TObject *Sender) {
   Button1->Caption = TypeIdName(Button1);
}

[2] Debo agradecer esta clarificación al Dr Stroustrup.

[3]  El mecanismo de comprobación de tipos en runtime (RTTI) es una incorporación tardía del lenguaje, de Marzo 1993.