int main(int, char**) {
double d = -12.3456789;
std::cout << d << std::endl;
float f = d;
std::cout << f << std::endl;
int i = d;
std::cout << i << std::endl;
uint32_t ui = d;
std::cout << ui << std::endl;
char ch = d;
std::cout << ch << std::endl;
return 0;
}
Какие касты существуют
- Implicit (неявное преобразование)
- Explicit (явные):
const_caststatic_castdynamic_castreinterpret_cast- C-style cast
Implicit cast
Происходит автоматически, когда компилятор умеет преобразовать один тип в другой без явного указания. Пример выше: double → float → int → uint32_t → char — всё это неявные преобразования, каждое из которых может потерять данные.
C-style cast
Синтаксис: (type)expression
int main(int, char**) {
int i = 1;
std::cout << *(double*)&i << std::endl;
}
Опасен тем, что компилятор не проверяет корректность — он просто делает то, что сказано. По сути это «умная» обёртка, которая последовательно пробует const_cast → static_cast → reinterpret_cast. Лучше использовать явные C++ касты.
const_cast
- Убирает
constилиvolatileс переменной. - Преобразует указатели и ссылки на одинаковые типы данных.
Использовать только если есть полная уверенность, что объект изначально не был константным — иначе undefined behavior. Обращаться очень аккуратно.
Типичные сценарии:
- В константном методе модифицировать что-то (когда сам объект изначально не константный).
- Совместимость с C-кодом, где API не принимает
const-указатели, но не модифицирует данные.
static_cast
- Работает в compile-time — быстро, без накладных расходов в рантайме.
- Пытается выполнить преобразование через конструкторы и операторы приведения типов.
- Подходит для стандартных типов (предпочтительная замена C-style касту).
- Умеет приводить указатели внутри одной иерархии классов (вверх и вниз).
- Умеет кастовать из
void*(обратно к конкретному типу).
Гарантия компилятора: если цепочка преобразований невозможна — код просто не скомпилируется.
Осторожно с кастом вниз по иерархии (Base* → Derived*):
- Если объект изначально и был
Derived— всё работает корректно. - Если объект изначально был
Base— формально получим «полноценный»Derived, но новые поля производного класса никак не проинициализированы → undefined behavior (могут лежать какие угодно байты — мусор). static_castне проверяет в рантайме, корректен ли каст вниз. Для этого естьdynamic_cast.
dynamic_cast
- Преобразует указатели и ссылки вверх и вниз по иерархии.
- Работает в runtime через RTTI (Runtime Type Identification).
- Использует таблицу виртуальных функций для проверки реального типа объекта.
Поведение при неудачном касте:
- Если кастуем указатель — вернёт
nullptr. - Если кастуем ссылку — бросит исключение
std::bad_cast.
Цена:
- Дополнительное время в рантайме на проверку.
- Класс обязан иметь хотя бы одну виртуальную функцию (иначе нет vtable → класс неполиморфный → ошибка компиляции).
reinterpret_cast
Страшно, бойтесь, противозаконно…
- Кастует несовместимые типы — просто переинтерпретирует побитовое представление памяти, никаких преобразований не делает.
- Компилятор ничего не проверяет и не предупреждает.
Нужно точно знать, что лежит в этом блоке памяти. Типичный легитимный сценарий — сериализация: кастуем указатель на структуру к char* и пишем байты в файл; при чтении кастуем обратно, зная размер структуры.
Подводные камни:
- Выравнивание (alignment) — у разных типов разные требования.
- Размер одних и тех же типов может различаться на разных платформах (
long,size_t). - Порядок байт — big-endian vs little-endian.
- Представление
float/doubleтоже может различаться.
Если запись и чтение происходят на одной и той же машине — об этих проблемах можно не беспокоиться.
Практическое применение — быстрая запись в бинарный файл: кастуем указатель на начало вектора к char* и пишем всё одним системным вызовом write. Это быстрее (один syscall вместо многих) и компактнее, чем текстовый вывод.