Преобразования типов и приведение
Превращаем один тип в другой - безопасно, осознанно и только тогда, когда действительно этого хотим
int в double, указатель на базовый класс в указатель на производный, дескриптор API в целое число. Часть этих преобразований происходит незаметно (компилятор вставляет их за вас), а часть вы обязаны запросить явно с помощью приведения. Понимание того, какие преобразования компилятор делает у вас за спиной, а какое из четырёх именованных приведений выбрать, когда вы берёте управление на себя, - это разница между кодом, корректность которого доказуема, и кодом, который тихо портит данные.
- распознавать неявные преобразования и замечать опасное сужение,
- использовать ключевое слово
explicit, чтобы запретить нежелательные преобразования, - правильно выбирать между
static_cast,dynamic_cast,const_castиreinterpret_cast, - объяснять, почему в современном C++ не рекомендуется старое приведение в стиле C
(T)x.
1. Неявные преобразования и сужение
Неявное преобразование - это преобразование, которое компилятор выполняет автоматически, без написанного вами приведения. Многие из них безобидны и полезны: присваивание int переменной типа double или передача char туда, где ожидается int. Это продвижения или расширяющие преобразования - тип-приёмник способен представить любое значение исходного типа, поэтому ничего не теряется.
Опасность - в обратном направлении: сужающее преобразование, когда тип-приёмник не может хранить каждое значение исходного типа. Присваивание double переменной int отбрасывает дробную часть; присваивание большого int переменной short может привести к переполнению и бессмысленному значению. Компилятор обычно молча это разрешает и в лучшем случае выдаёт предупреждение.
double pi = 3.14159;
int n = pi; // сужение: n становится 3, часть .14159 теряется
long big = 100000;
short s = big; // сужение: возможно переполнение, значение зависит от реализации
Современный C++ даёт вам страховку: инициализация в фигурных скобках {} превращает сужающее преобразование в ошибку компиляции, а не в тихий сюрприз. Это одна из лучших причин предпочитать фигурные скобки для инициализации.
int a = 3.9; // компилируется: a равно 3 (тихое сужение)
int b{3.9}; // ОШИБКА: сужающее преобразование внутри { } запрещено
Неявные преобразования также происходят через преобразующие конструкторы. Если у класса есть конструктор с одним аргументом, компилятор может использовать его, чтобы незаметно превратить тип этого аргумента в ваш класс - именно этим и учится управлять следующий раздел.