Несмотря на то что сайт описывает С++, в С++ иногда используют приёмы из языка С. И некоторые приёмы заслуживают того, чтобы плюсовики учли некоторые особенности используемых.
В С++ есть разные способы преобразований типов:
const_cast
static_cast
reinterpret_cast
dynamic_cast
Преобразование в стиле функций (functional cast)
Преобразование в стиле языка С
Каждое преобразование заслуживает своей отдельной темы. Сейчас мы ограничимся изучением преобразования в стиле языка С. Такой способ преобразования привлекателен своей простотой, но может быть достаточно опасным. Поскольку С++ является надмножеством языка С, то многое из языка С работает в С++. Одним из элементов языка С является способ преобразования типов. Способ, применяемый в языке С, когда название типа, к которому приводится некоторый приводимый тип, оборачивается скобками.
C++
1
2
3
4
5
6
7
8
9
//Листинг #1 Преобразования в стиле С
intmain(){
inta=10;
intb=20;
doublec=0;
c=(double)a/double(b);
}
В листинге #1 целое делится на целое, но мы чтобы получить число с точкой преобразуем целые типы int в типы double. На самом деле достаточно привести либо только числитель, либо только знаменатель, но суть не в этом. Мы определяем какой нам тип нужен для определённого значения и обозначаем выбранный тип в скобках. Путём некоторых незаметных нам манипулций компилятора создаётся дополнительная переменная, тип которой оказывается тем, что указан нами в круглых скобках, а значение в ту созданную временную переменную приходит из приводимого объекта. Никакое преобразование типов не преобразует напрямую типы, а создаёт дополнительные переменные нужного типа. Так вот, в стиле С нужный тип просто оборачивается в скобки, а после завёрнутого в скобки типа пишут или непосредственное значение, или объект. Этот синтаксис настолько прост, что чем больше его объяснять, тем он сложнее будет казаться.
Преобразование в стиле С схоже с преобразованием в стиле функционального преобразования. В функциональном преобразовании в скобки заворачивается не тип, а объект, тип которого преобразовывается. Несмотря на то что такой способ преобразования очень похож на способ преобразования в стиле С, срабатывают разные механизмы компилятора. Поэтому имейте в виду, что описание преобразования в стиле С не включает в себя преобразование в функциональном варианте.
C++
1
2
3
4
5
//Листинг #2
doublea=3.14;
intb=(int)a;//Преобразование в стиле С
intc=int(a);//Функциональное преобразование
Преобразование в стиле С срабатывает таким образом, что в зависимости от кода срабатывает одно из преобразований С++. Можно сказать, что у преобразования в С стиле есть некоторое право на самоопределение.
Преобразование в стиле С срабатывает на этапе компиляции, до начала выполнения программы.
Если происходит попытка преобразования в стиле С, то компилятор пробует определить, возможно ли произвести выбранное преобразование. Порядок проверки можно определит в виде очереди:
const_cast
static_cast
static_cast + const_cast
reinterpret_cast
reinterpret_cast + const_cast
Порядок можно запомнить на основе силы каста: константы самые слабые, а реинтрепрет самые сильные. Если никакое из этих преобразований невозможно, то код просто не сможет быть скомпилирован.
У преобразования в стиле С есть особенность поведения при касте указателей/ссылок на неполные типы. Тогда выбор static или reinterpret зависит от реализации компилятора
Преобразования в стиле С считаются очень опасными. Вам всегда будут говорить, что преобразование в стиле С опасно, не делайте такого. И это правильно, пока не знаете, какие проблемы могут случиться, не нужно использовать в С++ способа преобразования пришедшего из языка С.
Подведём итоги этой короткой статьи.
В языке С++ следует избегать преобразований вообще. А преобразований в стиле С следует избегать в тем более.
Пара примеров, демонстрирующих возникновение проблемы:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//Невнимательный программист перепутал названия и свёл всё к выводу на экран нулевого указателя, static_cast бы пресёк
#include <iostream>
usingstd::cout;
structOne{intvalue=10;};
structTwo{intvalue=20;};
intmain(){
One*po=0;
// Two * pt = (Two*)po;
Two*pt=newTwo;
pt=(Two*)po;
//pt = static_cast<Two*>(po); //static_cast не допустил бы
cout<<pt->value<<'\n';
delete pt;
}
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Невнимательный программист перепутал что нужно приводить программа перестала работать
#include <iostream>
usingstd::cout;
intmain(){
inti=10;
int*pi=&i;
cout<<*pi;
double*pd=(double*)pi;//static_cast бы не допустил, а так ошибка, код компилируется и программа запускается
/* Программист хотел вот этого
double *pd = new double ((double)*p_i);
cout << *pd;
delete pd;
*/
cout<<*pd<<'\n';
delete pd;
}
Много всяких опасностей таит в себе неумелое использование приведение в стиле языка С. Это очень мощный инструмент, он плох не сам по себе, а тем, что если вдруг возникает ошибка от неумелого его использования, то из-за того что ошибка может проявиться не сразу, а даже через несколько лет, очень сложно выискивать источник проблемы.
Добавить комментарий