OPERADOR
TYPEID
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 .
Sintaxis
typeid( expresion
)
typeid( nombre-de-tipo )
typeid( nombre-de-tipo )
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;
int x = 10, z = 20;
cout << typeid(x).name() << endl;
const type_info & ref = typeid(int);
cout << ref.name() << endl;
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 .
Ejemplo:
int x = 10, y =
20;
const type_info & tipo1 = typeid(x+z);
class C { int x; } c1;
const type_info & tipo2 = typeid(c1);
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);
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();
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
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 ye 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 clasetype_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:
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).
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 complicació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;
}
#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 !!!!
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.
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;
}
#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.
El ejemplo siguiente muestra el uso del operador typeid, y de los dos métodos before() y name() de la clasetype_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;
}
#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
int antes que double: 0
double antes que int: 1
A antes que B: 1
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;
}
#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.
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.
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.
}
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.
}
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 [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 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.
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 el comando -RT-, que provoca que no se genere código que permite la identificación de tipos en tiempo de ejecución. 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.
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 el comando -RT-, que provoca que no se genere código que permite la identificación de tipos en tiempo de ejecución. 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.
INICIO
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);
}
return typeid(*c).name();
}
// The button’s caption is set to TObject, not TButton.
void __fastcall TForm1::Button1Click(TObject *Sender) {
Button1->Caption = TypeIdName(Button1);
}
No hay comentarios:
Publicar un comentario