Касты — четыре механизма вместо одного
В большинстве языков есть один синтаксис каста. В C++ — четыре, и это не педантичность: за одним C-style (T)x может скрываться безопасное числовое преобразование, небезопасный downcast, снятие const или переинтерпретация байтов памяти — и компилятор не видит разницы. Именованные касты разрезают это пространство на четыре класса операций с разными гарантиями: каждый каст разрешает строго свой набор, а нарушение — ошибка компиляции.
C++ также различает три источника преобразований: implicit (вставляются компилятором — стандартные конверсии, integer promotion, narrowing), user-defined (через converting constructor или operator T()) и explicit (четыре именованных каста). Эти механизмы пересекаются: например, downcast в иерархии бывает и implicit (upcast), и static-checked, и runtime-checked (dynamic). Полная карта — в слоях ниже.
Карта темы
- Преобразования типов — общая карта: implicit/user-defined/explicit, lossy vs lossless.
- Неявные преобразования — integer promotion, narrowing, цепочки конверсий.
- Пользовательские преобразования — converting ctor,
operator T(),explicit. - Явные касты — четыре именованных, что делает C-style.
- Касты по иерархии — upcast, downcast, crosscast; static_cast vs dynamic_cast.
- Динамическая типизация и RTTI —
dynamic_cast,typeid,type_info,-fno-rtti. - Преобразование указателей —
void*,reinterpret_cast, strict aliasing,bit_cast. - Const-корректность —
const_cast, когда безопасно, когда UB,mutable.
Частые ошибки и ловушки
| Ошибка | Последствие |
|---|---|
C-style (T)x в новом коде | Молча может снять const или reinterpret — баг до runtime |
static_cast<Derived*>(base) без проверки типа | UB при использовании, если объект не Derived |
dynamic_cast от non-polymorphic типа | Ошибка компиляции — нет vtable, нет RTTI |
Запись через const_cast снятого с реально-const объекта | UB независимо от уровня каста |
Чтение через reinterpret_cast несовместимого типа | Strict aliasing violation — UB при оптимизации |
Брать dynamic_cast<T&> без try/catch | std::bad_cast при неудаче — необработанный exception |
Полиморфное удаление без virtual ~Base() | UB; деструктор Derived не вызовется |
Однопараметровый конструктор без explicit | Скрытые неявные конверсии в неожиданных местах |
int s = -1; if (s < unsigned(1)) | signed → unsigned даёт большое число, сравнение врёт |
int x{3.14} ожидая truncation | Ошибка компиляции — narrowing запрещён brace-init |
Значение для собеседований
Тема кастов всплывает на интервью потому, что напрямую связана с объектной моделью C++, RTTI, undefined behavior и strict aliasing. Интервьюер проверяет не знание синтаксиса, а понимание гарантий каждого каста.
Что обычно проверяют:
- Чем
static_castотличается от C-style cast — C-style перебирает несколько механизмов и может незаметно снятьconstили сделать reinterpret. - Когда
dynamic_castвозвращаетnullptr, а когда бросает — указатели vs ссылки. dynamic_castдля non-polymorphic класса — ошибка компиляции, и почему.- Strict aliasing — почему
reinterpret_castчтения чужого типа это UB. std::bit_cast— безопасная альтернатива, требования (sizeof+ trivially-copyable).const_cast— единственный путь снятьconst; UB при записи в реально-const.- Implicit user-defined conversion и правило «не более одной» в цепочке.
- Почему
explicitнужен на однопараметровом конструкторе.
Типичный неверный ответ: «Я везде использую (Type)value, это короче и делает то же самое». Именно с этого начинается обсуждение, что именно (T)x делает и где это ломается.