En programación informática, la información de tipo en tiempo de ejecución o identificación de tipo en tiempo de ejecución ( RTTI ) [1] es una característica de algunos lenguajes de programación (como C++ , [2] Object Pascal y Ada [3] ) que expone información sobre el tipo de datos de un objeto en tiempo de ejecución . La información de tipo en tiempo de ejecución puede estar disponible para todos los tipos o solo para los tipos que la tienen explícitamente (como es el caso de Ada). La información de tipo en tiempo de ejecución es una especialización de un concepto más general llamado introspección de tipo .
En el diseño original de C++, Bjarne Stroustrup no incluyó información de tipo en tiempo de ejecución, porque pensó que este mecanismo a menudo se usaba incorrectamente. [4]
En C++, RTTI se puede utilizar para realizar conversiones de tipos seguras mediante el dynamic_cast<>
operador y para manipular la información de tipos en tiempo de ejecución mediante el typeid
operador y la std::type_info
clase. En Object Pascal, RTTI se puede utilizar para realizar conversiones de tipos seguras con el as
operador, probar la clase a la que pertenece un objeto con el is
operador y manipular la información de tipos en tiempo de ejecución con clases contenidas en la RTTI
unidad [5] (es decir, clases: TRttiContext , TRttiInstanceType , etc.). En Ada, los objetos de tipos etiquetados también almacenan una etiqueta de tipo, que permite la identificación del tipo de estos objetos en tiempo de ejecución. El in
operador se puede utilizar para probar, en tiempo de ejecución, si un objeto es de un tipo específico y se puede convertir de forma segura a él. [6]
RTTI solo está disponible para clases que son polimórficas , lo que significa que tienen al menos un método virtual . En la práctica, esto no es una limitación porque las clases base deben tener un destructor virtual para permitir que los objetos de las clases derivadas realicen una limpieza adecuada si se eliminan de un puntero base.
Algunos compiladores tienen indicadores para desactivar RTTI. El uso de estos indicadores puede reducir el tamaño general de la aplicación, lo que los hace especialmente útiles cuando se utilizan sistemas con una cantidad limitada de memoria. [7]
La typeid
palabra reservada (palabra clave) se utiliza para determinar la clase de un objeto en tiempo de ejecución. Devuelve una referencia al std::type_info
objeto, que existe hasta el final del programa. [8] El uso de typeid
, en un contexto no polimórfico, a menudo se prefiere en situaciones donde solo se necesita la información de clase, porque siempre es un procedimiento de tiempo constante , mientras que puede necesitar recorrer la red de derivación de clase de su argumento en tiempo de ejecución. [ cita requerida ] Algunos aspectos del objeto devuelto están definidos por la implementación, como , y no se puede confiar en que sean consistentes en todos los compiladores.dynamic_cast<class_type>
typeid
dynamic_cast
std::type_info::name()
Los objetos de clase std::bad_typeid
se lanzan cuando la expresión for typeid
es el resultado de aplicar el operador unario * en un puntero nulo . Si se lanza una excepción para otros argumentos de referencia nula depende de la implementación. En otras palabras, para que se garantice la excepción, la expresión debe tener la forma typeid(*p)
donde p
es cualquier expresión que dé como resultado un puntero nulo.
#include <iostream> #include <información de tipo> clase Persona { público : virtual ~ Persona () = predeterminado ; }; clase Empleado : Persona pública {}; int main () { Persona persona ; Empleado empleado ; Persona * ptr = & empleado ; Persona & ref = empleado ; // La cadena devuelta por typeid::name está definida por la implementación. std :: cout << typeid ( persona ) .name () << std :: endl ; // Persona (conocida estáticamente en tiempo de compilación). std :: cout << typeid ( empleado ) .name () << std :: endl ; // Empleado (conocido estáticamente en tiempo de compilación). std :: cout << typeid ( ptr ) .name () << std :: endl ; // Persona* (conocida estáticamente en tiempo de compilación). std :: cout << typeid ( * ptr ) .name () << std :: endl ; // Empleado (buscado dinámicamente en tiempo de ejecución // porque es la desreferencia de un // puntero a una clase polimórfica). std :: cout << typeid ( ref ) .name () << std :: endl ; // Empleado (las referencias también pueden ser polimórficas) Persona * p = nullptr ; try { typeid ( * p ); // No es un comportamiento indefinido; lanza std::bad_typeid. } catch (...) { } Persona & p_ref = * p ; // Comportamiento indefinido: desreferenciar un typeid nulo ( p_ref ); // no cumple los requisitos para lanzar std::bad_typeid // porque la expresión para typeid no es el resultado // de aplicar el operador unario *. }
Salida (la salida exacta varía según el sistema y el compilador):
PersonaEmpleadoPersona*EmpleadoEmpleado
El dynamic_cast
operador en C++ se utiliza para convertir a la inversa una referencia o un puntero a un tipo más específico en la jerarquía de clases . A diferencia de static_cast
, el objetivo de dynamic_cast
debe ser un puntero o una referencia a la clase . A diferencia de la conversión de tipos static_cast
al estilo C (donde la comprobación de tipos se realiza durante la compilación), se realiza una comprobación de seguridad de tipos en tiempo de ejecución. Si los tipos no son compatibles, se lanzará una excepción (cuando se trate de referencias ) o se devolverá un puntero nulo (cuando se trate de punteros ).
Una conversión de tipos en Java se comporta de manera similar; si el objeto que se está convirtiendo no es en realidad una instancia del tipo de destino y no se puede convertir en uno mediante un método definido por el lenguaje, java.lang.ClassCastException
se lanzará una instancia de. [9]
Supongamos que una función toma un objeto de tipo A
como argumento y desea realizar alguna operación adicional si el objeto que se pasa es una instancia de B
, una subclase de A
. Esto se puede hacer de dynamic_cast
la siguiente manera.
#include <matriz> #include <iostream> #include <memoria> #include <información de tipo> utilizando el espacio de nombres std ; clase A { público : // Dado que RTTI está incluido en la tabla de métodos virtuales, debe haber al menos una función virtual. virtual ~ A () = predeterminado ; void MethodSpecificToA () { cout << "Se invocó el método específico para A" << endl ; } }; clase B : público A { público : void MethodSpecificToB () { cout << "Se invocó el método específico para B" << endl ; } }; void MyFunction ( A & my_a ) { try { // La conversión solo será exitosa para objetos de tipo B. B & my_b = dynamic_cast < B &> ( my_a ); my_b.MethodSpecificToB ( ) ; } catch ( const bad_cast & e ) { cerr << " Excepción " << e.what ( ) << " lanzada." << endl ; cerr << " El objeto no es de tipo B" << endl ; } } int main () { array < unique_ptr < A > , 3 > array_of_a ; // Matriz de punteros a la clase base A. array_of_a [ 0 ] = make_unique < B > (); // Puntero al objeto B. array_of_a [ 1 ] = make_unique < B > (); // Puntero al objeto B. array_of_a [ 2 ] = make_unique < A > (); // Puntero al objeto A. para ( int i = 0 ; i < 3 ; ++ i ) MiFuncion ( * matriz_de_una [ i ]); }
Salida de consola:
Se invocó el método específico para BSe invocó el método específico para BSe lanzó una excepción std::bad_cast.El objeto no es del tipo B
MyFunction
Se puede escribir una versión similar con punteros en lugar de referencias :
void MiFuncion ( A * mi_a ) { B * mi_b = conversion_dinamica < B *> ( mi_a ); si ( my_b != nullptr ) my_b -> methodSpecificToB (); de lo contrario std :: cerr << " El objeto no es de tipo B" << std :: endl ; }
En Object Pascal y Delphi , el operador is
se utiliza para comprobar el tipo de una clase en tiempo de ejecución. Prueba la pertenencia de un objeto a una clase dada, incluidas las clases de ancestros individuales presentes en el árbol de jerarquía de herencia (por ejemplo, Button1 es una clase TButton que tiene ancestros: TWinControl → TControl → TComponent → TPersistent → TObject , donde este último es el ancestro de todas las clases). El operador as
se utiliza cuando un objeto necesita ser tratado en tiempo de ejecución como si perteneciera a una clase ancestro.
La unidad RTTI se utiliza para manipular la información de tipo de objeto en tiempo de ejecución. Esta unidad contiene un conjunto de clases que permiten: obtener información sobre la clase de un objeto y sus antecesores, propiedades, métodos y eventos, cambiar valores de propiedades y llamar a métodos. El siguiente ejemplo muestra el uso del módulo RTTI para obtener información sobre la clase a la que pertenece un objeto, crearlo y llamar a su método. El ejemplo supone que la clase TSubject se ha declarado en una unidad denominada SubjectUnit.
utiliza RTTI , SubjectUnit ; procedimiento SinReflexión ; var MiAsunto : TAsunto ; comenzar MiAsunto := TAsunto . Crear ; intentar Asunto . Hola ; finalmente Asunto . Libre ; fin ; fin ; procedimiento WithReflection ; var RttiContext : TRttiContext ; RttiType : TRttiInstanceType ; Asunto : TObject ; comienzo RttiType := RttiContext . FindType ( 'SubjectUnit.TSubject' ) como TRttiInstanceType ; Asunto := RttiType . GetMethod ( 'Crear' ) . Invoke ( RttiType . MetaclassType , []) . AsObject ; prueba RttiType . GetMethod ( 'Hola' ) . Invoke ( Asunto , []) ; finalmente Asunto . Free ; fin ; fin ;