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 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 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
El operador typeid no puede
ser sobrecargado
4 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.
Cuando el
operador typeid se intenta
aplicar a un operando que es la deferencia de un puntero nulo ,
se lanza la excepción) 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 . 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 . 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.
4.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.
4.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
4.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.
4.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.
}
5 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
y permite la identificación incluso cuando el objeto solo es accesible
mediante un puntero o referencia . 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
operadordynamic_cast .
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 ( Soporte de runtime).
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 , 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 .
Si se activa la opción de habilitar limpieza total de destructores para
el manejo de excepciones 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 .
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:
Generalmente esta función-operador se utiliza para obtener el tipo de objetos referenciados mediante punteros o referencias. Situaciones como:
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 . Puede comprobarse con un sencillo ejemplo:
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):
4.1 Una versión del programa anterior que pusiera de manifiesto el lanzamiento de la excepción bad_typeid, sería el siguiente:
struct X {
int main() { // ==========
4.2 El ejemplo siguiente muestra el uso del operador typeid, y de los dos métodos before() y name() de la clase type_info.
// Uso de type_info::operator==() para hacer la comparación
// Uso de literales true y false para hacer la comparación
4.3 El siguiente ejemplo muestra la utilización de typeid para ilustrar una fuente de posibles errores en la declaración de punteros:
void main() { // ======================
int * pt3; int* pt4; // L.11: Ok. más seguro
4.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.
No hay comentarios:
Publicar un comentario