Ответ
В C++ для приведения типов используются четыре оператора *_cast, каждый со строго определенной семантикой, что заменяет опасное C-стилевое приведение (type)value.
1. static_cast
- Назначение: Стандартное, «разумное» приведение типов, проверяемое на этапе компиляции.
- Применение:
- Преобразования между числовыми типами (
int->float,enum->int). - Явное преобразование указателей/ссылок вверх и вниз по иерархии наследования (downcast без проверки времени выполнения).
- Преобразование
void*к конкретному типу указателя. - Вызов явных конструкторов преобразования или операторов приведения.
- Преобразования между числовыми типами (
-
Пример:
float f = 3.14; int i = static_cast<int>(f); // i = 3 (усечение) class Base {}; class Derived : public Base {}; Base* b = new Derived(); // Downcast: компилятор доверяет программисту. Derived* d = static_cast<Derived*>(b); // ОК, но если b не указывал на Derived -> UB
2. dynamic_cast
- Назначение: Безопасное приведение указателей/ссылок в иерархии полиморфных классов с проверкой во время выполнения (RTTI - Runtime Type Information).
- Применение: Преобразование вниз (downcast) или вбок (crosscast) по иерархии наследования.
- Условия: Работает только с типами, имеющими хотя бы одну виртуальную функцию (полиморфными).
- Поведение: Для указателей при неудаче возвращает
nullptr. Для ссылок — генерирует исключениеstd::bad_cast. -
Пример:
class Base { virtual void foo() {} }; // Полиморфный класс class Derived : public Base {}; Base* b1 = new Derived(); Base* b2 = new Base(); Derived* d1 = dynamic_cast<Derived*>(b1); // Успех, d1 != nullptr Derived* d2 = dynamic_cast<Derived*>(b2); // Неудача, d2 == nullptr try { Derived& rd1 = dynamic_cast<Derived&>(*b1); // Успех Derived& rd2 = dynamic_cast<Derived&>(*b2); // Выбросит std::bad_cast } catch (const std::bad_cast& e) { std::cerr << "Cast failed: " << e.what() << 'n'; }
3. reinterpret_cast
- Назначение: Низкоуровневое, «грубое» приведение, которое интерпретирует битовое представление значения одного типа как значение другого типа. Компилятор не выполняет никаких проверок.
- Применение:
- Преобразование между указателями на несвязанные типы (например,
Foo*вBar*). - Преобразование указателя в целочисленный тип (
uintptr_t) и обратно. - Преобразование в
void*и обратно для типов, не являющихся POD (с осторожностью).
- Преобразование между указателями на несвязанные типы (например,
- Опасность: Использование результата такого приведения часто ведет к неопределенному поведению, если нарушаются strict aliasing rules.
- Пример:
int* ip = new int(0xDEADBEEF); // Трактуем биты int как биты float (не преобразование значения!) float* fp = reinterpret_cast<float*>(ip); // Преобразование указателя в число (адрес памяти) uintptr_t addr = reinterpret_cast<uintptr_t>(ip);
4. const_cast
- Назначение: Добавление или удаление квалификаторов
constиvolatile. - Применение: В основном для вызова устаревших C-функций, которые принимают не-
constуказатели, но не модифицируют данные. - Важно: Применение
const_castдля модификации по-настоящему константного объекта (созданного какconst) приводит к неопределенному поведению. -
Пример:
void legacy_c_function(char* str); // Объявлена в C-библиотеке void modern_cpp_func(const char* input) { // legacy_c_function(input); // Ошибка компиляции: преобразование const char* -> char* legacy_c_function(const_cast<char*>(input)); // OK, если функция не меняет input } const int ci = 10; // int* pi = &ci; // Ошибка int* pi = const_cast<int*>(&ci); *pi = 20; // НЕОПРЕДЕЛЕННОЕ ПОВЕДЕНИЕ! Попытка изменить константный объект.
Общее правило выбора: Пытайтесь использовать static_cast. Для безопасного полиморфного downcast используйте dynamic_cast. К reinterpret_cast и const_cast прибегайте только в крайних случаях, четко понимая последствия.