TGViewer
Грокаем C++ Грокаем C++ @grokaemcpp · 9.35K subscribers
Post #272 3.59K
dynamic_cast

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

Бывает, что в рамках работы с полиморфными классами нам необходимо выполнить приведение от указателя с одним типом к другому из этого же полиморфного семейства. Зачастую мы не можем гарантировать, что динамический тип объекта совпадает с ожидаемым. Приведение оператором static_cast сопряжено с рисками получить UB. Как же нам безопасно его выполнить?

Отличительной особенностью dynamic_cast является проверка корректности приведения во время исполнения программы. Из живого примера 1:
Device *base = new Laptop();

// Try Laptop* -> Smartphone*
// Result: `derived` is `nullptr`
auto *derived = dynamic_cast<Smartphone*>(base);
...
// Try Laptop& -> Smartphone&
// Result: throw exception std::bad_cast
auto &derived = dynamic_cast<Smartphone&>(*base);


Как мы видим, компилятор позволяет собрать программу, но, в случае попытки приведения к ложному потомку, dynamic_cast возвращает либо нулевой указатель, либо бросает исключение std::bad_cast для ссылок.

Давайте сразу договоримся о цене таких преимуществ. Как вы догадываетесь, за эдакую роскошь приходится платить тактами процессора. Это подтверждают результаты бенчмарка. Это, действительно, в несколько десятков раз медленнее, но безопаснее! Ни о каком сравнении эффективности не может быть речи, если наша программа работает неправильно.

Когда же мы можем допустить ошибку? Давайте подумаем, какие вообще могут быть сценарии приведения:
1) От наследника к предку (up cast)
2) От предка к наследнику (down cast)
3) Между ветками подсемейств полиморфных классов (cross cast, side cast)

Кейс №1 достаточно тривиален. Мы знаем иерархию наследования, текущий тип объекта и нам надо лишь вычислить смещение до полей предка. Это можно сделать даже на этапе компиляции. Тут можно применить dynamic_cast, но достаточно и static_cast. Более того, если вы примените dynamic_cast, то все равно компилятор сгенерирует инструкции, аналогичные static_cast: живой пример 2.

Кейс №2 уже сложнее тем, что динамический тип объекта неизвестен на этапе компиляции. Его можно узнать только лишь в процессе выполнения программы, прочитав виртуальный указатель. Это как раз та ситуация, когда мы должны использовать dynamic_cast, чтобы быть готовым перехватить исключение или нулевой указатель. Так же, если в иерархии классов вы используете виртуальное наследование, то static_cast неприменим, т.к. смещение неизвестно для этого кейса.

Конечно, приведение можно попытаться выполнить с помощью оператора static_cast, чтобы было побыстрее! Но чем же это грозит? Можем выстрелить себе в ногу и начать работать с полученным объектом, как с объектом другого класса. Сравним разные операторы и продемонстрируем ошибку на живом примере 3. В общем случае, мы прочитаем что-то невнятное, а если изменим данные, то ещё и испортим память, что однозначно негативно скажется на всей программе. Попытка приведения оператором static_cast к ложному потомку правомерна с точки зрения типа. Ну правда, это тип из одной иерархии, и это будет работать, если случайно динамический тип объекта включает нужного потомка. Но при работе с семейством классов, как правило, вариантов потомков больше одного. Вот будет ли это поддерживаемым кодом? Можно ли безопасно вносить изменения в иерархию классов в будущем?

Кейс №3 декомпозируется на кейсы 1 + 2: выполняем приведение к общему предку, а затем выполняем от него приведение к требуемому наследнику. Следовательно, нам так же следует использовать dynamic_cast. Вспоминаем так же об особенностях представления памяти. Прикрепляю разбор на живом примере 4.

Резюмируем. Оператор dynamic_cast имеет преимущество с точки зрения безопасности и удобства, но он работает медленнее. Тут возникает вопрос, а за что вы переплачиваете? Если вам приходится использовать dynamic_cast, то это повод подумать, насколько хорошо продумана архитектура вашего решения. Не факт, что это плохая архитектура, но это повод её пересмотреть.

#cppcore
  • 🔥 25
  • 👍 7
  • ❤‍🔥 3
  • 👏 2
  • 🐳 2
  • ❤ 1
More from @grokaemcpp
  1. Oct 14, 2026​​Что внутри std::condition_variable? #опытным Более менее все знакомы с примитивом синхро…
  2. Oct 12, 2026​​WAT #новичкам Как вы думаете, результат выражения: bool math = -1 < 1U; будет правдой ил…
  3. Oct 8, 2026​​Strict weak ordering #опытным На первый взгляд, всё выглядит рабочим: мы создаём 40 зака…
  4. Oct 7, 2026​​Где-то баг... #опытным Вот вам код: struct Order { int price; int id; }; int main() { st…
  5. Oct 5, 2026Откуда spurious wakeup на кондваре? #опытным У кондваров есть метод std::condition_variabl…
  6. Oct 1, 2026​​Stacktrace. Tips #опытным Чтобы полноценно работать со стандартными трейсами, нужно знат…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →