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)
objимеет типconst Base&, но объект в памяти —Derived.- Класс полиморфный (есть
virtual ~Base()). typeid(obj)возвращает ссылку наstd::type_infoдля динамического типа —Derived.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_cast | dynamic_cast | |
|---|---|---|
| Проверка в runtime | нет | да (для полиморфных типов) |
| Скорость | быстрее | дороже |
| Безопасность при неизвестном подтипе | риск UB | nullptr или 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
Связанные материалы
- ООП в C++ — vtable, virtual, abstract
- Обработка исключений — try/catch при
bad_cast - Системное программирование — проекты без RTTI
См. также
Другие статьи этого же раздела в боковом меню (как на странице «О разделе»). C++ как язык системного программирования - ключевые принципы, область применения и инженерные требования к коду. Экосистема приложений на C++ - области применения языка от системного ПО до высоконагруженных вычислений. C++ — это мощный язык программирования общего назначения, который обеспечивает прямой доступ к аппаратным ресурсам компьютера при сохранении высокой производительности. Гайд по установке и настройке с написанием первой программы и её запуском. Директива препроцессора include используется для подключения заголовочных файлов в исходный код. Она сообщает компилятору вставить содержимое указанного файла в текущее место перед началом компиляции. Конфигурация — это набор правил и переменных, которые управляют процессом превращения исходного текста в исполняемый продукт. Примеры простых и полезных консольных приложений с демонстрацией концепций языка. Минимальный кроссплатформенный проект C++17 с CMake — структура, сборка и разбор CMakeLists построчно. Модульные тесты с GTest и Catch2 в CMake-проекте — зачем отдельный target, примеры и запуск ctest. Набор мини-проектов для закрепления C++ — консоль, RAII, CMake, Qt, ranges и опционально Vulkan. Набор советов, правил, принципов и обычаев в разработке на этом языке. Типизация, набор правил определения типа данных значений языка.C++ - язык системного программирования
Экосистема приложений на C++
Что требуется знать перед началом изучения языка программирования C++
Первая программа на C++
Начало работы с C++
Конфигурация и сборка в C++
Простые приложения на C++
CMake — первая программа
Google Test и Catch2 в C++
Практические задания по C++
Рекомендации по разработке на C++
Типы данных в C++