Сегодня попробуем разобраться в том, как происходят преобразования типов в С++

Начнем с static_cast. Его проверки происходят на этапе компиляции. Кастовать можно базовые типы к друг другу

double y = 0.06;
int x = static_cast<int>(y);

Так же можно преобразовывать связанные типы. Например, класс потомок к базовому типу — upcast, так и в обратную сторону от базового класса к потомку — downcast.

struct Base
{
 
};
 
struct Derived : public Base
{
 
};
int main(int argc, char *argv[])
{
    Derived derived;
    //upcast
    Base *base = static_cast<Base*>(&derived);
 
    //downcast
    Derived *derived2 = static_cast<Derived*>(base);
    return 0;
}

Однако в данном случае нужно понимать, что проверяется только связанность типов. Возьмем пример ниже:

struct Base
{
 
};
 
struct Derived : public Base
{
 
};
int main(int argc, char *argv[])
{
    Base base;
    Derived *derived = static_cast<Derived*>(&base);
 
    return 0;
}

Мы создаем объект базового класса и его кастуем к классу потомку. Так как объекты связанные, то на этапе компиляции формально ошибок никаких не будет. Однако надо понимать, что в данных которые соответствуют Derived будет лежать мусор и использование его данных приведет к неопределенному поведению UB.

Следующий тип преобразований — const_cast. Может понадобиться для снятия константности у каких либо данных. Возьмем такой пример, который в теории может случиться на практике. У нас есть функция, которая на вход принимает тип char* и у нас есть строка string data.

void PrintData(char* data)
{
    cout << data << endl;
}
 
int main(int argc, char *argv[])
{
    string data = "hello";
 
    PrintData(data.c_str());
    return 0;
}

Хотя формально тут нет ошибки, но данный пример не скомпилируется, так как data.c_str() возвращает const char*, а не char*. Поэтому в данном случае можно воспользоваться const_cast.

PrintData(const_cast<char *>(data.c_str()));

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

Еще один случай который нам нужно рассмотреть это reinterpret_cast. Допустим мы получили откуда нибудь из UART данные int data[] = {29, 5, 2022}; которые говорят нам о том, какой сейчас день, месяц и год. И допустим у нас есть структура, с которой можно удобно работать с датами struct Days. Раскладка по памяти одинаковая, а данные разные, как нам преобразовать одни данные в другие. Вот тут можно будет воспользоваться reinterpret_cast.

#include <iostream>
 
using namespace std;
 
struct Days
{
    int day;
    int month;
    int year;
};
 
void PrintData(Days& d)
{
    cout << "day=" << d.day << endl;
    cout << "month=" << d.month << endl;
    cout << "year=" << d.year << endl;
}
 
int main(int argc, char *argv[])
{
    int data[] = {29, 5, 2022};
 
    PrintData(reinterpret_cast<Days&>(data));
    return 0;
}

Заключение. Сегодня мы рассмотрели 3 варианта преобразования типов. Про dynamic_cast уже упоминалось в статье про RTTI. Важно понимать, что reinterpret_cast, const_cast, static_cast проверяются в compile time, а dynamic_cast в runtime. Где и какое преобразование применять зависит от того какие необходимо производить манипуляции. Важно понимать что static_cast может сдвигать указатель при преобразованиях, а reinterpret_cast нет.

2 комментария: Type casting

  • Зачем такие сложности, интересно. На С я пишу просто PrintData(&data); и всё работает и так…

  • Некоторые из этих операций будут эквивалентны тому, что вы пишите на Си и более того взаимозаменяемы. До тех пор пока вы не будете использовать шаблонную магию. По сути static_cast и reinterpret_cast просто дополнительно говорят компилятору проверять, связаны эти типы или нет.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика