Практический курс по ООП C++: https://stepik.org/a/205781
На этом занятии
мы с вами отдельно поговорим об операторах преобразования типов, которые были
введены в язык C++. Как вы помните, в курсе по языку Си мы уже
говорили про операцию приведения типов и часто ей пользовались. Например, с ее
помощью можно указателем с типом char просмотреть побайтно содержимое любой
переменной:
int a = 10;
char * ptr = (char *)&a; // приведение типа int* к char *
Однако эта
операция не так безобидна, как может показаться на первый взгляд. Например,
можно константный указатель сделать обычным, не константным:
int a = 10;
const int* ptr_cnst = &a;
int * ptr = (int *)ptr_cnst;
Или, с типом char работать,
словно это тип int:
char a = 10;
int* ptr_a = (int *)&a;
И так далее.
Операции преобразования типов легко могут стать причинами трудноуловимых
ошибок. Тем более что программисту здесь дается полная свобода действий, без
каких-либо особых ограничений со стороны компилятора. И эту анархию решил хоть
как-то ограничить разработчик языка C++ Бьерн Страуструп, введя для
преобразования типов четыре новых оператора:
- const_cast – снимает или
добавляет модификатор const (и volatile) для текущего
типа данных;
- reinterpret_cast
– преобразование одного типа в другой, с некоторой дополнительной проверкой со
стороны компилятора на возможность указанного действия;
- static_cast – преобразование
типов с учетом цепочки наследования классов или структур;
- dynamic_cast –
преобразование типов с учетом цепочки наследования классов или структур в
процессе выполнения программы (динамически).
Давайте
детальнее посмотрим, как они работают и чем отличаются от обычной операции
приведения типов, которую по-прежнему можно использовать в языке C++.
Оператор const_cast
Первый оператор const_cast просто снимает или
добавляет модификатор const к текущему типу
данных. Работает он следующим образом:
int a = 10;
const int* ptr_a = &a;
int * ptr = const_cast<int *>(ptr_a); // снятие const
const int * ptr_cnst = const_cast<const int *>(&a); // добавление const
Обратите
внимание, как он записан. После его имени обязательно следуют угловые скобки, в
которых прописывается тип, к которому приводится указатель, а затем, в круглых
скобках идет сам указатель. Результат присваивается другому указателю того же
типа.
Ту же самую
операцию можно записывать и для ссылок:
const double a = 10;
const double& lnk_a_var = a;
double& lnk_a = const_cast<double &>(lnk_a_var); // снятие const
const double& lnk_a_cnst = const_cast<const double &>(lnk_a); // добавление const
А вот с обычными
переменными функция const_cast работать не
будет:
double b = const_cast<double>(a);
В качестве
аргумента допускается прописывать либо указатель, либо ссылку.
Также, если типы
приводимых данных не будут совпадать:
float& lnk_a = const_cast<double &>(lnk_a_var);
то тоже
возникнет ошибка на этапе компиляции. То есть, const_cast только снимает/добавляет
модификатор const у ссылок и
указателей без изменения их базового типа.
Конечно, у вас
может возникнуть вопрос, зачем все же понадобилось вводить этот оператор, если
он по-прежнему вносит в программу небезопасное поведение? Наверное, модификатор
const прописывается
не случайно и снимать его не следует? Все верно. Именно поэтому нужно избегать
использования оператора const_cast в тексте
программы. Если вдруг он вам по каким-либо причинам потребовался, то лучше
пересмотреть структуру программы и понять, почему так произошло. Исправить этот
момент без привлечения небезопасного оператора const_cast. И только в
очень, очень редких случаях его применение оправданно.
Оператор reinterpret_cast
Следующий
оператор reinterpret_cast очень похож по
своему действию на обычную операцию приведения типов языка Си, но работает с
некоторыми ограничениями.
Во-первых, он
применим все так же к указателям и ссылкам. С обычными переменными работать не
будет. Например:
int a = 10;
int b = reinterpret_cast<int>(a); // ok
char c = reinterpret_cast<char>(a); // ошибка
Если изменение
типа не происходит, то выполнится обычное присвоение (инициализация) одной
переменной другой переменной. Если же указать разные типы, то возникнет ошибка.
Во-вторых, оператор
reinterpret_cast не может снимать модификаторы const и volatile. За это, как мы
уже знаем, отвечает другой оператор const_cast и их действия
не пересекаются. Например:
int a = 10;
const int* ptr_a_cnst = &a;
int* ptr_a = reinterpret_cast<int *>(ptr_a_cnst); // ошибка
Во всем
остальном он похож на обычную операцию приведения типов языка Си.
Преобразование типов между указателями:
int a = 10;
int* ptr_a = &a;
char* ptr_ch = reinterpret_cast<char *>(ptr_a);
void* ptr_v = reinterpret_cast<void *>(ptr_a);
double* ptr_d = reinterpret_cast<double *>(ptr_a);
Преобразование
типов между ссылками:
double b = 0.5;
double &lnk_b = b;
int& lnk_bi = reinterpret_cast<int&>(lnk_b);
long double& lnk_bld = reinterpret_cast<long double&>(lnk_b);
Также мы можем
делать приведение типов и между пользовательскими типами данных. Например,
объявим две структуры:
struct point {
int x, y;
};
struct point3D {
int x, y, z;
};
И, затем:
point pt {1, 2};
point3D* ptr_3d = reinterpret_cast<point3D *>(&pt);
point& lnk_pt = reinterpret_cast<point &>(*ptr_3d);
В языке C++ предпочтение
отдается именно таким операторам преобразования типов, а не классической
операции языка Си.
На следующем
занятии мы продолжим эту тему и рассмотрим две оставшихся операции static_cast и dynamic_cast.
Практический курс по ООП C++: https://stepik.org/a/205781