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 typeidoperador e a std::type_infoclasse. Em Object Pascal, o RTTI pode ser usado para realizar casts de tipo seguro com o asoperador, testar a classe à qual um objeto pertence com o isoperador e manipular informações de tipo em tempo de execução com classes contidas na RTTIunidade (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 inoperador 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_infoobjeto, 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>typeiddynamic_caststd::type_info::name()

Objetos de classe std::bad_typeidsã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_castoperador 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_castdeve ser um ponteiro ou referência à classe . Ao contrário static_caste 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.ClassCastExceptionserá lançada.

Exemplo

Suponha que alguma função tome um objeto do tipo Acomo 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_casto 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 MyFunctionpode 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: TWinControlTControlTComponentTPersistentTObject , 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;

Veja também

Referências

links externos