В программировании информация о типе во время выполнения или идентификация типа во время выполнения ( RTTI ) [1] является функцией некоторых языков программирования (таких как C++ , [2] Object Pascal и Ada [3] ), которая раскрывает информацию о типе данных объекта во время выполнения . Информация о типе во время выполнения может быть доступна для всех типов или только для типов, которые явно имеют ее (как в случае с Ada). Информация о типе во время выполнения является специализацией более общей концепции, называемой интроспекцией типа .
В оригинальном проекте C++ Бьярне Страуструп не включил информацию о типе времени выполнения, поскольку считал, что этот механизм часто используется неправильно. [4]
В C++ RTTI может использоваться для выполнения безопасных приведений типов с помощью dynamic_cast<>
оператора и для манипулирования информацией о типе во время выполнения с помощью typeid
оператора и std::type_info
класса. В Object Pascal RTTI может использоваться для выполнения безопасных приведений типов с помощью as
оператора, проверки класса, к которому принадлежит объект is
, с помощью оператора и манипулирования информацией о типе во время выполнения с помощью классов, содержащихся в RTTI
модуле [5] (т. е. классов: TRttiContext , TRttiInstanceType и т. д.). В Ada объекты тегированных типов также хранят тег типа, который позволяет идентифицировать тип этих объектов во время выполнения. Оператор in
может использоваться для проверки во время выполнения, имеет ли объект определенный тип и может ли он быть безопасно преобразован в него. [6]
RTTI доступен только для полиморфных классов , что означает, что у них есть по крайней мере один виртуальный метод . На практике это не ограничение, поскольку базовые классы должны иметь виртуальный деструктор , чтобы позволить объектам производных классов выполнять надлежащую очистку, если они удаляются из базового указателя.
Некоторые компиляторы имеют флаги для отключения RTTI. Использование этих флагов может уменьшить общий размер приложения, что делает их особенно полезными при работе с системами с ограниченным объемом памяти. [7]
Зарезервированное typeid
слово (ключевое слово) используется для определения класса объекта во время выполнения. Оно возвращает ссылку на объект, который существует до конца программы. [8] Использование в неполиморфном контексте часто предпочтительнее, чем в ситуациях , когда нужна только информация о классе, поскольку это всегда процедура с постоянным временем выполнения, тогда как может потребоваться пройти по решетке вывода класса своего аргумента во время выполнения. [ необходима цитата ] Некоторые аспекты возвращаемого объекта определяются реализацией, например , и на них нельзя полагаться в разных компиляторах для обеспечения согласованности.std::type_info
typeid
dynamic_cast<class_type>
typeid
dynamic_cast
std::type_info::name()
Объекты класса std::bad_typeid
выбрасываются, когда выражение for typeid
является результатом применения унарного оператора * к нулевому указателю . Выбрасывается ли исключение для других аргументов нулевой ссылки, зависит от реализации. Другими словами, чтобы исключение было гарантировано, выражение должно иметь вид, typeid(*p)
где p
— любое выражение, приводящее к нулевому указателю.
#include <iostream> #include <typeinfo> класс Person { public : virtual ~ Person () = default ; }; класс Сотрудник : публичный Человек {}; int main () { Person person ; Employee employee ; Person * ptr = & employee ; Person & ref = employee ; // Строка, возвращаемая typeid::name, определяется реализацией. std :: cout << typeid ( person ). name () << std :: endl ; // Person (статически известен во время компиляции). std :: cout << typeid ( employee ). name () << std :: endl ; // Employee (статически известен во время компиляции). std :: cout << typeid ( ptr ). name () << std :: endl ; // Person* (статически известен во время компиляции). std :: cout << typeid ( * ptr ). name () << std :: endl ; // Employee (ищет динамически во время выполнения, // поскольку это разыменование указателя // на полиморфный класс). std :: cout << typeid ( ref ). name () << std :: endl ; // Сотрудник (ссылки также могут быть полиморфными) Person * p = nullptr ; try { typeid ( * p ); // Не неопределенное поведение; выдает std::bad_typeid. } catch (...) { } Person & p_ref = * p ; // Неопределенное поведение: разыменование null typeid ( p_ref ); // не соответствует требованиям для генерации std::bad_typeid // потому что выражение для typeid не является результатом // применения унарного оператора *. }
Вывод (точный вывод зависит от системы и компилятора):
ЧеловекСотрудникЧеловек*СотрудникСотрудник
Оператор dynamic_cast
в C++ используется для приведения ссылки или указателя к более конкретному типу в иерархии классов . В отличие от static_cast
, целью dynamic_cast
должен быть указатель или ссылка на класс . В отличие от static_cast
и приведения типов в стиле C (где проверка типов происходит во время компиляции), проверка безопасности типов выполняется во время выполнения. Если типы несовместимы, будет сгенерировано исключение (при работе со ссылками ) или будет возвращен нулевой указатель (при работе с указателями ).
Приведение типа в Javajava.lang.ClassCastException
ведет себя аналогичным образом: если преобразуемый объект на самом деле не является экземпляром целевого типа и не может быть преобразован в него методом, определенным в языке, будет выдан экземпляр . [9]
Предположим, что некоторая функция принимает объект типа в качестве аргумента и хочет выполнить некоторую дополнительную операцию , A
если переданный объект является экземпляром B
, подклассомA
. Это можно сделать dynamic_cast
следующим образом.
#include <массив> #include <поток_вывода> #include <память> #include <тип_информации> с использованием пространства имен std ; class A { public : // Поскольку RTTI включен в таблицу виртуальных методов, должна быть по крайней мере // одна виртуальная функция. virtual ~ A () = default ; void MethodSpecificToA () { cout << "Был вызван метод, специфичный для A" << endl ; } }; class B : public A { public : void MethodSpecificToB () { cout << "Был вызван метод, специфичный для B" << endl ; } }; void MyFunction ( A & my_a ) { try { // Приведение будет успешным только для объектов типа B. B & my_b = dynamic_cast < B &> ( my_a ); my_b . MethodSpecificToB (); } catch ( const bad_cast & e ) { cerr << " Исключение " << e . what () << " Вызвано." << endl ; cerr << " Объект не относится к типу B" << endl ; } } int main () { array < unique_ptr < A > , 3 > array_of_a ; // Массив указателей на базовый класс A. array_of_a [ 0 ] = make_unique < B > (); // Указатель на объект B. array_of_a [ 1 ] = make_unique < B > (); // Указатель на объект B. array_of_a [ 2 ] = make_unique < A > (); // Указатель на объект A. для ( int i = 0 ; i < 3 ; ++ i ) МояФункция ( * array_of_a [ i ]); }
Вывод на консоль:
Был вызван метод, специфичный для B.Был вызван метод, специфичный для B.Возникло исключение std::bad_cast.Объект не относится к типу B
Похожую версию MyFunction
можно записать с указателями вместо ссылок :
void MyFunction ( A * my_a ) { B * my_b = dynamic_cast < B *> ( my_a ); если ( my_b != nullptr ) my_b -> methodSpecificToB (); иначе std :: cerr << "Объект не является типом B" << std :: endl ; }
В Object Pascal и Delphi оператор is
используется для проверки типа класса во время выполнения. Он проверяет принадлежность объекта к данному классу, включая классы отдельных предков, присутствующих в дереве иерархии наследования (например, Button1 — это класс TButton , имеющий предков: TWinControl → TControl → TComponent → TPersistent → TObject , где последний является предком всех классов). Оператор as
используется, когда объект необходимо обрабатывать во время выполнения так, как если бы он принадлежал классу-предку.
Модуль RTTI используется для манипулирования информацией о типе объекта во время выполнения. Этот модуль содержит набор классов, которые позволяют: получать информацию о классе объекта и его предках, свойствах, методах и событиях, изменять значения свойств и вызывать методы. В следующем примере показано использование модуля RTTI для получения информации о классе, к которому принадлежит объект, его создании и вызове его метода. В примере предполагается, что класс TSubject был объявлен в модуле с именем SubjectUnit.
использует 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 ;