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

RTTI в C++ — typeid и dynamic_cast

Разработчику Архитектору

:::tip О чём эта статья RTTI (Run-Time Type Information) — способ узнать реальный тип объекта уже после запуска программы и безопасно привести указатель или ссылку в иерархии классов. Нужен реже, чем кажется новичку: чаще достаточно виртуальных методов. Но для отладки, плагинов и редкой навигации по дереву типов RTTI незаменим. :::

Статический и динамический тип

Когда вы пишете:

Base* ptr = new Derived();

у переменной ptr два «типа»:

ТипКогда известенВ примере
Статическийпри компиляцииBase* — компилятор считает, что указатель на Base
Динамическийв момент выполненияобъект в памяти — Derived

Обычные вызовы через ptr идут по правилам статического типа, кроме виртуальных функций — там вызывается метод динамического типа. RTTI отвечает на вопрос: «А кто на самом деле лежит за этим Base*

Полиморфизм и vtable: 14. Оператор typeid в контексте операторов: 12.


Что такое RTTI

RTTI — механизм C++, встроенный в полиморфные классы (с хотя бы одной virtual функцией, обычно виртуальным деструктором).

Основные инструменты:

ИнструментЗаголовокНазначение
typeid<typeinfo>имя типа, сравнение type_info
dynamic_castвстроенныйприведение с проверкой иерархии

Без виртуальных функций класс неполиморфныйtypeid и dynamic_cast для иерархии такого класса не дадут динамической информации о подтипе.


typeid

#include <typeinfo>
#include <iostream>

class Base {
public:
virtual ~Base() = default;
};

class Derived : public Base {};

void print_type(const Base& obj) {
const std::type_info& ti = typeid(obj);
std::cout << ti.name() << '\n';
}

int main() {
Derived d;
print_type(d);
std::cout << (typeid(d) == typeid(Derived)) << '\n';
}

Разбор выражения typeid(obj)

  1. obj имеет тип const Base&, но объект в памяти — Derived.
  2. Класс полиморфный (есть virtual ~Base()).
  3. typeid(obj) возвращает ссылку на std::type_info для динамического типа — Derived.
  4. ti.name() — строка с именем типа; у GCC/Clang часто mangled (внутреннее имя компилятора), у MSVC может быть читабельнее.

Когда тип статический

  • Для неполиморфных типов typeid(T) и typeid(expr) дают статический тип expr.
  • Для указателя без разыменования typeid(ptr) — тип указателя (Base*), а не объекта.

std::type_info сравнивают через == и !=. Копировать и присваивать type_info нельзя — это синглтоноподобные объекты рантайма.

Где уместен typeid: логирование, отладка, фабрики «по имени типа». Для ветвления бизнес-логики чаще лучше виртуальный метод, enum class Kind или std::variant на закрытом наборе типов.


dynamic_cast

Приведение типа — «считать указатель/ссылку другого типа в иерархии». static_cast доверяет программисту; dynamic_cast проверяет в рантайме, возможно ли такое приведение.

Указатели — при неудаче — nullptr

Base* base = new Derived();
if (auto* derived = dynamic_cast<Derived*>(base)) {
derived->specific_method();
}
delete base;

Разбор:

ЧастьСмысл
dynamic_cast<Derived*>(base)«Если base реально указывает на Derived (или наследника Derived), верни Derived*; иначе nullptr»
if (auto* derived = ...)C++17: объявление переменной прямо в условии
Вызов specific_method()Безопасен только внутри ветки, где приведение успешно

Ссылки — при неудаче — исключение

void use(Base& b) {
try {
Derived& d = dynamic_cast<Derived&>(b);
d.specific_method();
} catch (const std::bad_cast&) {
// b ссылался на объект, который не является Derived
}
}

Для ссылок нет значения «нулевая ссылка», поэтому ошибка — std::bad_cast (191).

Когда dynamic_cast уместен

УместноЛучше другой приём
редкая навигация (UI-элементы, узлы сцены)длинные цепочки if (dynamic_cast<A*>...) else if (dynamic_cast<B*>...)
плагины с общим базовым интерфейсомдублировать то же через virtual int kind() без RTTI
клонирование / сериализация по типу«зоопарк» типов вместо Visitor

Предпочтение в прикладном коде: virtual метод clone(), accept(Visitor&), закрытый enum class + switch.


Сравнение с static_cast

Derived* p = static_cast<Derived*>(base_ptr);

static_cast не проверяет иерархию в runtime. Если base_ptr указывал на другой подтип, вызов методов Derived даёт неопределённое поведение (часто падение).

static_castdynamic_cast
Проверка в runtimeнетда (для полиморфных типов)
Скоростьбыстреедороже
Безопасность при неизвестном подтипериск UBnullptr или bad_cast

Другие приведения: 12, чек-лист 999.


Стоимость и отключение RTTI

Компилятор хранит таблицы типов для полиморфных классов (рядом с vtable). Флаги отключения:

  • GCC/Clang: -fno-rtti
  • MSVC: /GR-

В embedded и игровых движках RTTI иногда отключают ради размера бинарника. Тогда typeid и dynamic_cast недоступны. Обзор «pay for what you use»: 28.


Мини-шпаргалка

Полиморфный класс (virtual) ──► можно typeid / dynamic_cast
Нужна ветка логики по типу ──► сначала virtual / Visitor / variant
Нужно привести указатель ──► dynamic_cast + проверка на nullptr
Нужно привести ссылку ──► dynamic_cast + catch bad_cast

Связанные материалы


См. также

Другие статьи этого же раздела в боковом меню (как на странице «О разделе»).