Перейти к содержимому

Rtti c что это

  • автор:

Сведения о типах времени выполнения

Информация о типах времени выполнения (RTTI) — это механизм, позволяющий определить тип объекта во время выполнения программы. Функция RTTI была добавлена в язык C++, поскольку многие поставщики библиотек классов реализовывали ее самостоятельно. Это приводило к проблемам совместимости между библиотеками. Таким образом, стало очевидно, что необходима поддержка информации о типах времени выполнения на уровне языка.

Для ясности этот раздел о RTTI почти полностью посвящен указателям. Однако рассматриваемые понятия также применяются ко ссылкам.

Существует три основных элемента языка C++ для информации о типах времени выполнения.

  • Оператор dynamic_cast . Используется для преобразования полиморфных типов.
  • Оператор typeid . Используется для указания точного типа объекта.
  • Класс type_info . Используется для хранения сведений о типе, возвращаемых оператором typeid .

См. также

Дополнительные ресурсы

Значок отказа согласно Закону Калифорнии о защите конфиденциальности потребителей (CCPA)

  • Светлая
  • Темная
  • Высокая контрастность
  • Предыдущие версии
  • Блог
  • Участие в доработке
  • Конфиденциальность
  • Условия использования
  • Товарные знаки
  • © Microsoft 2023

Дополнительные ресурсы

Значок отказа согласно Закону Калифорнии о защите конфиденциальности потребителей (CCPA)

  • Светлая
  • Темная
  • Высокая контрастность
  • Предыдущие версии
  • Блог
  • Участие в доработке
  • Конфиденциальность
  • Условия использования
  • Товарные знаки
  • © Microsoft 2023

Идентификация типа во время исполнения (RTTI)

Используя идентификацию типа времени исполнения (run-time type identification — RTTI) можно определить тип объекта во время исполнения программы. Для этого используется функция typeid. Для использования этой функции необходимо включить заголовочный файл typeinfo.h. Общая форма записи функции typeid имеет следующий вид:

Здесь объект является объектом, чей тип требуется определить. Функция typeid возвращает ссылку на объект типа typeinfo, который описывает тип объекта объект. (В проекте стандарта С++ этот тип называется type_info.) Класс typeinfo определяет следующие публичные члены:

bool operator== (const typeinfo &ob) const;
bool operator!= (const typeinfo &ob) const;
bool before(const typeinfo &ob) const;
const char *name() const;

Перегруженные операторы == и != обеспечивают сравнение типов. Функция before() возвращает истину, если вызываемый объект стоит выше в иерархии объектов, чем объект, используемый в качестве параметра. Функция before() предназначена большей частью для внутреннего использо­вания. Возвращаемое ею значение не имеет ничего общего с иерархией классов или наследовани­ем. Функция name() возвращает указатель на имя типа.

Когда функция typeid применяется к указателю на базовый класс полиморфного класса, она автоматически возвращает тип объекта, на который указывает указатель, в том числе любой класс, выведенный из базового класса. (Как уже говорилось, полиморфным классом называется класс, содержащий хотя бы одну виртуальную функцию.)

ПАМЯТКА: Указатель обязательно должен быть разыменован. Следует также установить режим поддержки RTTI в опциях компилятора.

Следующая программа демонстрирует использование функции typeid

Программа выдаст следующий результат на экран:

Typeid of i is int
p is pointing to an object of type BaseClass
p is pointing to an object of type Derived1
p is pointing to an object of type Derived2

Как уже отмечалось, применение функции typeid к указателю на базовый класс полиморфного типа позволяет определить фактический тип объекта, на который указывает указатель во время исполнения программы. Поэкспериментируем теперь, закомментировав виртуальную функцию f() в базовом классе, и посмотрим на результат.

RTTI не является тем средством, которое используется всякой программой. Однако при работе с полиморфными типами она позволяет знать тип объекта в любой ситуации.

C++, как работает RTTI

Я бы хотел понять, как в C++ работает RTTI . Мне понятно, как вызываются виртуальные методы. Это происходит через таблицу виртуальных методов, указатель на эту таблицу размещается в объекте каждого класса, который содержит виртуальные методы. У каждого класса с виртуальными методами есть своя таблица виртуальных методов. Но мне не совсем понятно, как во время исполнения определяется тип объекта. В объектах размещается какая-то дополнительная информация? Например, ID типа. И как следствие, я не понимаю, велико ли отличие между ручным приведением типов при помощи dynamic_cast и использованием паттерна visitor для реализации двойной диспетчеризации.

Отслеживать
задан 10 авг 2019 в 1:32
4,128 1 1 золотой знак 9 9 серебряных знаков 22 22 бронзовых знака

В разных компиляторах может быть сделано по-разному. Вот, к примеру, небольшая заметка по RTTI в какой-то из версий студии.

10 авг 2019 в 6:14

«использованием паттерна visitor для реализации двойной диспетчеризации» Можно пример, как это выглядит?

Магия C++. Как выжить без динамической идентификации типов и не сойти с ума

В C++ существует такое понятие, как динамическая идентификация типа данных (RTTI). Это механизм, который позволяет определить тип переменной или объекта на этапе выполнения программы. Однако, чтобы уменьшить размер собранных бинарей, во многих проектах RTTI отключают, и от этого перестают работать dynamic_cast и typeid. Но способ проверить, порожден ли инстанс объекта от какого-то базового класса, все же есть, и в своей статье я покажу, как сделать это элегантно.

Отключение RTTI приводит к тому, что для верификации начинают делать разные странные вещи — например, заводят целочисленные class_id и проверяют их вручную, бегая глазами по иерархии классов. Мы же используем магию шаблонов и полиморфизм, чтобы обойтись без этого. Это поможет уменьшить количество бредокода, а также упростит приведение к нужному типу.

Традиционно C++ считается надежным инструментом построения API с проверкой типов еще на этапе компиляции. Исключения составляют шаблоны и ссылки на базовый тип, которые дают относительную свободу в выборе типа. Как правило, когда проект разрастается, стараются экономить каждый мегабайт получающихся бинарников и в первую очередь под нож идет система RTTI.

С одной стороны, конечно, RTTI дает возможность делать dynamic_cast вверх по иерархии наследования, а также узнавать идентификатор типа по typeid, но, с другой стороны, кто и когда этим пользуется? Как правило, dynamic_cast без проблем заменяется static_cast (если библиотека C++ не содержит действительно ветвистое дерево наследования).

Бывает, конечно, что наследование в C++ не сводится к наличию банального базового интерфейса IClassName и наследованию ClassName в недрах библиотеки. Вместо этого может быть представлена полноценная иерархия типов. Тогда без RTTI будет сложно обойтись, просто потому, что мы не сможем взять и проверить тип на инстанцирование определенного типа иерархии через проверку dynamic_cast.

void somefunc(base* b) < if (derived* d = dynamic_cast(b)) 

Как правило, либо есть стопроцентная уверенность, что это инстанс определенного класса (которая в половине случаев в итоге оказывается далеко не стопроцентной), либо инстанс типа проверяется на некий уникальный CLASS_ID, переданный при создании экземпляра класса. А это, разумеется, крайне дырявый способ проверить инстанцирование класса, ведь наследники будут иметь свой уникальный CLASS_ID. Поэтому такая проверка возможна не столько на имплементацию класса, сколько на соответствие ровно одному типу. Все это выливается в целые цепочки проверок вида

void somefunc(const creature& c) < if (c.class_id() == animal::CLASS_ID) . if (c.class_id() == cow::CLASS_ID) . 

Однако идея, в общем-то, неплоха, давай только сделаем проверку проще и эффективнее. Пусть у нас будет возможность проверить любые два класса иерархии на наследование, а любой инстанс класса этой иерархии — на имплементацию определенного класса.

Делаем проверку удобнее

Помогут нам в этом шаблоны и полиморфизм. Если у нас всего два класса, то, кроме шаблона, нам ничего не нужно.

Итак, дано дерево наследования, пусть каждый класс X однозначно идентифицируется по методу X::id() , а также задан typedef для базового класса, поименованный как X::base для каждого класса иерархии.

class B : public A < public: typedef A base; static someunique id(); 

В этом случае шаблон is_class::of() будет достаточно простым: нужна проверка на соответствие X::id() или непосредственно Y::id() , либо X — один из наследников Y , и, рекурсивно обходя предков, мы найдем соответствие.

template struct is_class < template static bool of() < return X::id() == Y::id() || is_class::of(); > 

Теперь осталось только обеспечить корректный выход из рекурсии в базовом классе. Для корректного обхода классов иерархии нам нужно, чтобы базовый класс завершал поиск предка.

template <> struct is_class  < template static bool of()

Теперь если есть иерархия creature => animal => cat и animal => dog , то можно спокойно проверять любые классы на наследование друг от друга.

template void meet(X&& x) < if (is_class::of()) feed(std::forward(x)); 

Полиморфизм вместо RTTI

С проверкой объекта на инстанцирование класса иерархии будет сложнее. Нам понадобится специальный виртуальный метод who() , который в каждом классе возвращает свой статический уникальный id() .

class B : public A

Также было бы удобно у любого инстанса иерархии спрашивать, инстанцирует ли он один из ее классов. Для этого в корневом классе иерархии заведем метод is() , который будет шаблонным, а значит, несколько поломает нам полиморфизм при проверке. Для выхода из ситуации заведем еще один вспомогательный метод is_base_id , перегруженный от типа, которым определяется уникальность класса в методе id() .

class root < public: template bool is() const < return is_base_id(X::id()); >virtual bool is_base_id(const someunique& base_id) const

У наследников метод is_base_id должен переопределяться аналогично проверке is_class::of с использованием проверки базового класса на соответствие id() .

class B : public A < public: typedef A base; static someunique id(); virtual someunique who() const < return id(); >virtual bool is_base_id(const someunique& base_id) const

Таким образом, каждый класс иерархии можно будет проверить на инстанцирование любого ее класса и безопасно приводить тип по ссылке в соответствии с иерархией.

void somefunc(const creature& c) < if (c.is()) < const animal& a = static_cast(c); 

Поскольку после проверки на инстанцирование чаще всего идет именно приведение типа (обычно для этого проверка и нужна), то удобно также в корневом классе сделать еще один метод as() , который инкапсулирует проверку is() и статическое преобразование типа.

class root < public: template const X& as() const; template X& as(); 

Разумеется, это удобно делать, только если вместе с RTTI в проекте не отключены еще и исключения. Иначе будет сложно кинуть исключение в том случае, если, например, некий объект x типа animal не инстанцирует тип cat , а его просят x.as().say_meow() . ��

Уметь готовить C++

Язык C++ очень гибкий и мощный, а если немножко уделять внимание деталям, то и удобный. Мы получили высокоуровневые способы удобной проверки наследования классов и инстанцирования от них объектов. Накладные расходы при этом не так страшны.

  • Наследуемый класс должен определять предка через typedef с тем же именем, например base.
  • Каждый класс иерархии должен уметь идентифицировать себя методом id() . Тип идентификатора я рассмотрю ниже, но он непринципиален, это может быть просто int .
  • Корневой класс иерархии должен завершать рекурсивный обход вверх по иерархии, что весьма логично.
  • Каждый класс иерархии должен уметь проверить произвольный id() на соответствие либо своему id() , либо id() одного из своих предков — для проверки инстанцирования класса.

Также для удобства были введены методы who() , is() и as() в базовом классе.

Единственный минус этой схемы — обязательное объявление base , is_base_id() , id() и who() в каждом классе иерархии. Для удобства можно завернуть все в макрос с аргументом базового типа, делающий однотипные объявления.

Теперь внутри иерархии классов мы можем:

  • проверять, является ли класс X наследником другого класса Y , конструкцией is_class::of() ;
  • проверять, инстанцирует ли объект z класс W методом z.is() .

Бонусом мы получили рантайм-информацию об идентификаторе типа методом who() . Если идентификатор поддерживает человекочитаемый формат, это может быть полезно для логирования и отладки.

Другим бонусом может стать безопасное приведение типов вверх по иерархии вспомогательным методом as() , даже если исключения отключены. Просто в этом случае придется возвращать указатель и проверять его в лучших традициях пещерных предков, писавших на чистом C.

Уникальный идентификатор класса

Осталось разобраться, как идентифицировать класс. На самом деле подойдет абсолютно любой тип, поддерживающий сравнение. Имеет смысл создавать константу с идентификатором в первый вызов метода id() каждого класса.

Будет полезно, если при этом по id() окажется возможным будет понять, чей это идентификатор, то есть не лишним будет имя типа. Также важно понимать, что тело метода должно находиться не в заголовочном файле, а идентификатор должен единожды экспортировать уникальную сгенерированную константу.

Рассмотрим пример такого типа, по которому можно идентифицировать наследование любого класса иерархии.

class class_id < public: class_id(const char* const name) : m_name(name), m_index(generate_unique()) < >const char* const name() const < return m_name; >int index() const < return m_index; >bool operator == (const class_id& another) const < return m_index == another.m_index; >private: const char* const m_name; int m_index; >; 

Если мы используем int , то его достаточно просто потокобезопасно инкрементить для генерации уникальных значений и получить миллиарды разных типов. Выходит, что class_id в целом необязателен, но узнать у объекта иерархии z его z.who().name() бывает очень и очень полезно.

В методе id() каждого класса нам в помощь static-константа с нужным именем.

const class_id& cat::id()

Таким образом, все объявления методов id() и who() сведутся к объявлениям вида

class creature

Подчеркну, что генерация уникальных констант не должна инлайниться, а должна экспортироваться из единственного места, где создан ровно один экземпляр данного типа для каждого класса иерархии, включая инстансы шаблонов.

Безопасность — в удобстве

В очередной раз можно убедиться, что чем удобнее мы делаем для себя окружение разработки, тем безопаснее получается код и тем проще его поддерживать. Вместо грозящего нам леса проверок на идентификаторы в каждом втором методе мы автоматизировали проверку обхода всей иерархии до самого ее корня и запаковали в удобные читаемые методы.

Слабое место такого подхода — принудительное инстанцирование шаблонов наследников. Также этот метод не получится применять для проверки более одного предка, но можно добавить необходимые модификации.

В общем, мой совет — не мириться с вещами, которые вызывают сложности в поддержке кода — твоего или унаследованного. Простейшее объявление одного-единственного макроса в теле класса и объявление по одному методу аналогичного id() для класса решит любые проблемы с рекурсивной проверкой наследования и инстанцирования классов иерархии.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *