Informazioni sul tipo di runtime - Run-time type information
Nella programmazione del computer, le informazioni sul tipo di runtime o l' identificazione del tipo di runtime ( RTTI ) è una funzionalità di alcuni linguaggi di programmazione (come C++ , Object Pascal e Ada ) che espone le informazioni sul tipo di dati di un oggetto in fase di esecuzione . Le informazioni sul tipo di runtime possono essere disponibili per tutti i tipi o solo per i tipi che le hanno esplicitamente (come nel caso di Ada). Le informazioni sul tipo di runtime sono una specializzazione di un concetto più generale chiamato introspezione del tipo .
Nel progetto C++ originale, Bjarne Stroustrup non includeva informazioni sul tipo di runtime, perché pensava che questo meccanismo fosse spesso usato in modo improprio.
Panoramica
In C++, RTTI può essere utilizzato per eseguire typecast sicuri , utilizzando l' dynamic_cast<>operatore, e per manipolare le informazioni sul tipo in fase di esecuzione, utilizzando l' typeidoperatore e la std::type_infoclasse. In Object Pascal, RTTI può essere utilizzato per eseguire cast di tipi sicuri con l' asoperatore, testare la classe a cui appartiene un oggetto con l' isoperatore e manipolare le informazioni sul tipo in fase di esecuzione con le classi contenute RTTInell'unità (ad esempio le classi: TRttiContext , TRttiInstanceType , eccetera.). In Ada, gli oggetti di tipi con tag memorizzano anche un tag di tipo, che consente l'identificazione del tipo di questi oggetti in fase di esecuzione. L' inoperatore può essere utilizzato per verificare, in fase di esecuzione, se un oggetto è di un tipo specifico e può essere convertito in esso in sicurezza.
RTTI è disponibile solo per le classi che sono polimorfiche , il che significa che hanno almeno un metodo virtuale . In pratica, questa non è una limitazione perché le classi base devono avere un distruttore virtuale per consentire agli oggetti delle classi derivate di eseguire una corretta pulizia se vengono eliminati da un puntatore di base.
Alcuni compilatori hanno flag per disabilitare RTTI. L'utilizzo di questi flag può ridurre le dimensioni complessive dell'applicazione, rendendoli particolarmente utili quando si prendono di mira sistemi con una quantità limitata di memoria.
C++ – typeid
La typeid parola chiave viene utilizzata per determinare la classe di un oggetto in fase di esecuzione . Restituisce un riferimento a std::type_infooggetto, che esiste fino alla fine del programma. L'uso di typeid, in un contesto non polimorfico, è spesso preferito in situazioni in cui sono necessarie solo le informazioni sulla classe, perché è sempre una procedura a tempo costante , mentre potrebbe essere necessario attraversare il reticolo di derivazione della classe del suo argomento in fase di esecuzione. Alcuni aspetti dell'oggetto restituito sono definiti dall'implementazione, ad esempio , e non è possibile fare affidamento sui compilatori per essere coerenti.
dynamic_cast<class_type>typeiddynamic_caststd::type_info::name()
Gli oggetti di classe std::bad_typeidvengono lanciati quando l'espressione for typeidè il risultato dell'applicazione dell'operatore unario * su un puntatore null . Il fatto che venga generata un'eccezione per altri argomenti di riferimento null dipende dall'implementazione. In altre parole, affinché l'eccezione sia garantita, l'espressione deve assumere la forma typeid(*p)dove pè qualsiasi espressione risultante in un puntatore nullo.
Esempio
#include <iostream>
#include <typeinfo>
class Person {
public:
virtual ~Person() = default;
};
class Employee : public Person {};
int main() {
Person person;
Employee employee;
Person* ptr = &employee;
Person& ref = employee;
// The string returned by typeid::name is implementation-defined.
std::cout << typeid(person).name()
<< std::endl; // Person (statically known at compile-time).
std::cout << typeid(employee).name()
<< std::endl; // Employee (statically known at compile-time).
std::cout << typeid(ptr).name()
<< std::endl; // Person* (statically known at compile-time).
std::cout << typeid(*ptr).name()
<< std::endl; // Employee (looked up dynamically at run-time
// because it is the dereference of a
// pointer to a polymorphic class).
std::cout << typeid(ref).name()
<< std::endl; // Employee (references can also be polymorphic)
Person* p = nullptr;
try {
typeid(*p); // Not undefined behavior; throws std::bad_typeid.
} catch (...) { }
Person& p_ref = *p; // Undefined behavior: dereferencing null
typeid(p_ref); // does not meet requirements to throw std::bad_typeid
// because the expression for typeid is not the result
// of applying the unary * operator.
}
Output (l'output esatto varia in base al sistema e al compilatore):
Person Employee Person* Employee Employee
C++ – dynamic_cast e Java cast
L' dynamic_castoperatore in C++ viene utilizzato per eseguire il downcast di un riferimento o un puntatore a un tipo più specifico nella gerarchia di classi . A differenza di static_cast, la destinazione di dynamic_castdeve essere un puntatore o un riferimento alla classe . A differenza static_caste C-style typecast (dove tipo di controllo viene effettuata durante la compilazione), un controllo di sicurezza di tipo viene eseguito a tempo di esecuzione . Se i tipi non sono compatibili, verrà generata un'eccezione (quando si tratta di riferimenti ) o verrà restituito un puntatore nullo (quando si tratta di puntatori ).
Un typecast Java si comporta in modo simile; se l'oggetto di cui viene eseguito il cast non è effettivamente un'istanza del tipo di destinazione e non può essere convertito in uno da un metodo definito dal linguaggio, java.lang.ClassCastExceptionverrà lanciata un'istanza di .
Esempio
Supponiamo che una funzione prenda un oggetto di tipo Acome argomento e desideri eseguire alcune operazioni aggiuntive se l'oggetto passato è un'istanza di B, una sottoclasse di A. Ciò può essere ottenuto utilizzando dynamic_castcome segue.
#include <array>
#include <iostream>
#include <memory>
#include <typeinfo>
using namespace std;
class A {
public:
// Since RTTI is included in the virtual method table there should be at
// least one virtual function.
virtual ~A() = default;
void MethodSpecificToA() {
cout << "Method specific for A was invoked" << endl;
}
};
class B: public A {
public:
void MethodSpecificToB() {
cout << "Method specific for B was invoked" << endl;
}
};
void MyFunction(A& my_a) {
try {
// Cast will be successful only for B type objects.
B& my_b = dynamic_cast<B&>(my_a);
my_b.MethodSpecificToB();
} catch (const bad_cast& e) {
cerr << " Exception " << e.what() << " thrown." << endl;
cerr << " Object is not of type B" << endl;
}
}
int main() {
array<unique_ptr<A>, 3> array_of_a; // Array of pointers to base class A.
array_of_a[0] = make_unique<B>(); // Pointer to B object.
array_of_a[1] = make_unique<B>(); // Pointer to B object.
array_of_a[2] = make_unique<A>(); // Pointer to A object.
for (int i = 0; i < 3; ++i)
MyFunction(*array_of_a[i]);
}
Uscita console:
Method specific for B was invoked Method specific for B was invoked Exception std::bad_cast thrown. Object is not of type B
Una versione simile di MyFunctionpuò essere scritta con puntatori invece di riferimenti :
void MyFunction(A* my_a) {
B* my_b = dynamic_cast<B*>(my_a);
if (my_b != nullptr)
my_b->methodSpecificToB();
else
std::cerr << " Object is not B type" << std::endl;
}
Delphi / Oggetto Pascal
In Object Pascal, l'operatore isviene utilizzato per verificare il tipo di una classe in fase di esecuzione . Viene verificata l'appartenenza di un oggetto in una data classe, incluse le classi dei singoli antenati presenti nella struttura gerarchia ereditaria (es Button1 è un TButton classe che ha antenati: TWinControl → TControl → TComponent → TPersistent → TObject , dove quest'ultima è antenato di tutte le classi). L'operatore asviene utilizzato quando un oggetto deve essere trattato in fase di esecuzione come se appartenesse a una classe antenata.
L'unità RTTI viene utilizzata per manipolare le informazioni sul tipo di oggetto in fase di esecuzione. Questa unità contiene un insieme di classi che consentono di: ottenere informazioni sulla classe di un oggetto e sui suoi antenati, proprietà, metodi ed eventi, modificare i valori delle proprietà e chiamare i metodi. L'esempio seguente mostra l'utilizzo del modulo RTTI per ottenere informazioni sulla classe a cui appartiene un oggetto, crearlo e chiamarne il metodo. L'esempio presuppone che la classe TSubject sia stata dichiarata in un'unità denominata SubjectUnit.
uses
RTTI, SubjectUnit;
procedure WithoutReflection;
var
MySubject: TSubject;
begin
MySubject := TSubject.Create;
try
Subject.Hello;
finally
Subject.Free;
end;
end;
procedure WithReflection;
var
RttiContext: TRttiContext;
RttiType: TRttiInstanceType;
Subject: TObject;
begin
RttiType := RttiContext.FindType('SubjectUnit.TSubject') as TRttiInstanceType;
Subject := RttiType.GetMethod('Create').Invoke(RttiType.MetaclassType, []).AsObject;
try
RttiType.GetMethod('Hello').Invoke(Subject, []);
finally
Subject.Free;
end;
end;