Informações de tipo de tempo de execução - Run-time type information
Em programação de computador, informações de tipo em tempo de execução ou identificação de tipo em tempo de execução ( RTTI ) é um recurso de algumas linguagens de programação (como C ++ , Object Pascal e Ada ) que expõe informações sobre o tipo de dados de um objeto em tempo de execução . As informações de tipo de tempo de execução podem estar disponíveis para todos os tipos ou apenas para os tipos que as possuem explicitamente (como é o caso com Ada). As informações de tipo em tempo de execução são uma especialização de um conceito mais geral denominado introspecção de tipo .
No projeto C ++ original, Bjarne Stroustrup não incluía informações de tipo de tempo de execução, porque ele pensava que esse mecanismo era frequentemente mal utilizado.
Visão geral
Em C ++, o RTTI pode ser usado para fazer previsões dedynamic_cast<>
tipos seguras , usando o operador, e para manipular informações de tipo em tempo de execução, usando o typeid
operador e a std::type_info
classe. Em Object Pascal, o RTTI pode ser usado para realizar casts de tipo seguro com o as
operador, testar a classe à qual um objeto pertence com o is
operador e manipular informações de tipo em tempo de execução com classes contidas na RTTI
unidade (ou seja, classes: TRttiContext , TRttiInstanceType , etc.). Na Ada, os objetos do tipo com tag também armazenam uma tag de tipo, que permite a identificação do tipo desses objetos em tempo de execução. O in
operador pode ser usado para testar, em tempo de execução, se um objeto é de um tipo específico e pode ser convertido com segurança para ele.
O RTTI está disponível apenas para classes polimórficas , o que significa que têm pelo menos um método virtual . Na prática, isso não é uma limitação porque as classes base devem ter um destruidor virtual para permitir que os objetos de classes derivadas executem a limpeza adequada se forem excluídos de um ponteiro base.
Alguns compiladores possuem sinalizadores para desabilitar RTTI. O uso desses sinalizadores pode reduzir o tamanho geral do aplicativo, tornando-os especialmente úteis ao direcionar sistemas com uma quantidade limitada de memória.
C ++ - typeid
A typeid
palavra - chave é usada para determinar a classe de um objeto em tempo de execução . Ele retorna uma referência ao std::type_info
objeto, que existe até o final do programa. O uso de typeid
, em um contexto não polimórfico, é geralmente preferido em situações em que apenas as informações da classe são necessárias, porque é sempre um procedimento de tempo constante , embora possa precisar percorrer a rede de derivação de classe de seu argumento em tempo de execução. Alguns aspectos do objeto retornado são definidos pela implementação, como e não podem ser considerados consistentes entre os compiladores.
dynamic_cast<class_type>
typeid
dynamic_cast
std::type_info::name()
Objetos de classe std::bad_typeid
são lançados quando a expressão for typeid
é o resultado da aplicação do operador unário * em um ponteiro nulo . Se uma exceção é lançada para outros argumentos de referência nula depende da implementação. Em outras palavras, para que a exceção seja garantida, a expressão deve assumir a forma em typeid(*p)
que p
é qualquer expressão que resulte em um ponteiro nulo.
Exemplo
#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.
}
Saída (a saída exata varia de acordo com o sistema e compilador):
Person Employee Person* Employee Employee
C ++ - dynamic_cast e Java cast
O dynamic_cast
operador em C ++ é usado para fazer downcast de uma referência ou ponteiro para um tipo mais específico na hierarquia de classes . Ao contrário de static_cast
, o destino de dynamic_cast
deve ser um ponteiro ou referência à classe . Ao contrário static_cast
e C-estilo estereotipado (onde verificação de tipo é feita durante a compilação), uma verificação de segurança tipo é realizado no tempo de execução . Se os tipos não forem compatíveis, uma exceção será lançada (ao lidar com referências ) ou um ponteiro nulo será retornado (ao lidar com ponteiros ).
Um typecast Java se comporta de maneira semelhante; se o objeto que está sendo lançado não for realmente uma instância do tipo de destino e não puder ser convertido em um por um método definido pela linguagem, uma instância de java.lang.ClassCastException
será lançada.
Exemplo
Suponha que alguma função tome um objeto do tipo A
como seu argumento e deseje realizar alguma operação adicional se o objeto passado for uma instância de B
, uma subclasse de A
. Isso pode ser feito usando dynamic_cast
o seguinte.
#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]);
}
Saída do console:
Method specific for B was invoked Method specific for B was invoked Exception std::bad_cast thrown. Object is not of type B
Uma versão semelhante de MyFunction
pode ser escrita com ponteiros em vez de referências :
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 / Object Pascal
Em Object Pascal, o operador is
é usado para verificar o tipo de uma classe em tempo de execução . Ele testa a pertença de um objeto a uma determinada classe, incluindo classes de ancestrais individuais presentes na árvore de hierarquia de herança (por exemplo, Button1 é uma classe TButton que tem ancestrais: TWinControl → TControl → TComponent → TPersistent → TObject , onde o último é o ancestral de todas as classes). O operador as
é usado quando um objeto precisa ser tratado em tempo de execução como se pertencesse a uma classe ancestral.
A unidade RTTI é usada para manipular informações de tipo de objeto em tempo de execução. Esta unidade contém um conjunto de classes que permitem: obter informações sobre a classe de um objeto e seus ancestrais, propriedades, métodos e eventos, alterar valores de propriedade e métodos de chamada. O exemplo a seguir mostra o uso do módulo RTTI para obter informações sobre a classe à qual um objeto pertence, criá-lo e chamar seu método. O exemplo assume que a classe TSubject foi declarada em uma unidade chamada 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;