January 12, 2021

Явное преобразование типов в С++

В языке C++ есть 5 видов операций явного преобразования типов:

  • конвертация C-style;
  • применение оператора static_cast;
  • применение оператора const_cast;
  • применение оператора dynamic_cast;
  • применение оператора reinterpret_cast.

С-style преобразование

С-style приведения типов данных доступен в языке C++, но считается не самодостаточным. С-style приведения типов данных может быть использован для преобразования любого типа в любой другой тип, при этом неважно насколько это небезопасное преобразование, например, преобразование целого числа в указатель типа int. Казалось бы, такое преобразование невозможно, однако компилятор выполнит это приведение. Какой получится результат компилятору не важно. Синтаксис приведения С-style:

double res = (double)13 / 2; // res == 6.5

В этом примере тип int приводится к типу double, чтобы избежать усечения результата из-за целочисленного деления.

Метод приведения типов С-style не делает проверки типов на совместимость, как это могут сделать static_cast и dynamic_cast.

static_cast преобразование

Оператор приведенияstatic_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_castот С-style преобразования состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот, а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы.

int x = 10;
double d;
int* p;
d = static_cast<double>(x);
p = static_cast<int*>(x); // ошибка

Рекомендуется пользоваться операцией static_cast, нежели С-style приведением, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.

dynamic_cast преобразование

Оператор приведения dynamic_castприменяется для полиморфного приведения типов на этапе выполнения программы. Класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция. Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего, то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен nullptr, если приводятся указатели. Если приведение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_castпредназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерархии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_castприводить к указателю на void, но не может приводить указатель на void к другому типу.

struct A{
    //Сделали полиморфным
	virtual void do_some(){};
};

struct B:A{ };
 
int main()
{
    A a = new B();
    B *b = dynamic_cast<B>(a);
}

Чтобы безопасно пользоваться динамическим приведением, все вызовы dynamic_cast должны быть обрамлены в блок try/catch и/или после преобразования проверены в конструкции if.

const_cast преобразование

Оператор приведения const_castудаляет или добавляет квалификаторы constи volatileс исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал intили наоборот.

Квалификаторы constи volatileназывают cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.

    int *x = new int(10);
    const int *p;
    p = const_cast<const int*>(x);
	*p = 33; //Ошибка !!!

reinterpret_cast преобразование

Оператор приведения reinterpret_castиспользуется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом приведения типов в стиле языка С. Отличие состоит в том, что reinterpret_castне может снимать квалификаторы constи volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению.

int a[] = { 1,2,3,4,5,6,7,8,9,10 };
auto out = *reinterpret_cast<int (*) [3][3]>(a);
// out == {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}