В чем разница между static_cast, dynamic_cast, reinterpret_cast и const_cast в C++?

«В чем разница между static_cast, dynamic_cast, reinterpret_cast и const_cast в C++?» — вопрос из категории C++ Core, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В 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 прибегайте только в крайних случаях, четко понимая последствия.