Явное приведение типа const_cast используют для того, чтобы отбросить квалификатор const у изначально не константных данных или добавить квалификатор const.
const_cast не влияет на оригинальную переменную: не убирает её const и не добавляет ей const
ВНИМАНИЕ!
Отбрасывание квалификатора const у переменных, которые изначально живут с приписанным им квалификатором const, может привести к неопределённому поведению программы. Т. е. любые попытки изменить переменную, с момента рождения которой было приписано не изменяться, ведёт к непрогнозируемому поведению, т. е. UB.
Вот один из забавных примеров, в котором, в зависимости от компилятора и его оптимизатора, две переменные могут попасть на один адрес, чего по сути быть не может, и, конечно, это только иллюзия, а не действительность. В любом случае, в нижепоказанном коде идёт попытка снятия константности с изначально константного значения, что однозначно есть неопределённое поведение.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
usingnamespacestd;
intmain()
{
constinti=100;
constint*ptr=&i;
int*pi=const_cast<int*>(&i);//Снятие const с переменной, на которую указывает pi
(*pi)=200;//Изменение переменной, которая обозначена const
cout<<(void*)&i << '\t' << i << '\n';//На экране можно увидеть один адрес
cout<<(void*)pi<<'\t'<<*pi<<'\n';//Но разные значения
}
В данной ситуации const_cast снимает константность при преобразовании типа. Поскольку переменная i была изначально константной, её адрес имел тип const int*. Благодаря const_cast с типа адреса была снята константность, и его стало возможно поместить в переменную, на которую указывает b, где эта переменная есть не const целое число. Значение i на самом деле изменяется. То, что на экран выводится её предыдущее значение (в некоторых, но не во всех компиляторах) — результат некорректной работы оптимизатора, который ошибочно предположил, что объявленная как const переменная i изменяться не будет, и подставил то значение, которое указано ещё при инициализации.
В любом случае — изменение переменной, которая инициализирована как const — это неопределённое поведение работы программы.
Возникает очень логичный вопрос: какой тогда смысл в использовании const_cast? Ответ на этот вопрос на поверхности:
Иногда нужно изменять значение, которое изначально не квалифицировано как константное, но в какой-нибудь функции, в ходе выполнения цепочки функций, успело заболеть свойством константности.
Простой пример проявления подобной болезни и лечения от константы рождённого неконстантным существа:
C++
1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
usingnamespacestd;
voidfoo(char*){}
intmain()
{
charconst*str="";//какая-то строка
// foo(str); //Нет функции foo, принимающей указатель на const char
foo(const_cast<char*>(str));//снятие константности позволяет использовать функцию
}
Это опасный пример, потому что надо точно знать, что внутри функции foo указатель char не будет изменять того, на что указывает.
Поговорим об опасностях. Посмотрите на пример, пример плохой. Потому что поведение программы, собранной из такого кода, предсказать невозможно. Этот пример UB.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
usingnamespacestd;
voidchange(constint*pt,intn){
int*pc;
pc=const_cast<int*>(pt);//<-- ключевой момент
*pc+=n;//<-- ключевой момент
}
intmain(){
intpop1=38383;
constintpop2=2000;//<-- ключевой момент
cout<<"pop1, pop2: "<<pop1<<','<<pop2<<'\n';
change(&pop1,-103);
change(&pop2,-103);//<-- ключевой момент
cout<<"pop1, pop2: "<<pop1<<','<<pop2<<'\n';
}
В этом примере идёт попытка изменить константное значение pop2. Этот пример может сработать, но поведение программы из-за этого изменения не определено.
Если отбросить всё, что может нарушить целостность const переменных, то пример вполне рабочий, но опять же опасный, потому что какой-нибудь нехороший ломастер обязательно создаст указатель на константу, который отдаст функции, и в итоге будет неопределённое поведение программы.
Теперь поговорим о хорошем. Вот хороший пример, хотя, в показываемом виде, непрактичный:
*pc+=n;//<-- изменение переменной, которая изначально имеет право меняться
}
intmain(){
intpop1=38383;//<-- В самом начале определено, что переменная изменяется
cout<<"pop1, pop2: "<<pop1<<'\n';
change(&pop1,-103);
cout<<"pop1, pop2: "<<pop1<<'\n';
}
Некоторые люди считают, что const_cast только снимает константность, но в действительности эта хитрая штука не только снимает константность, но и добавляет её; точнее, с помощью неё можно добавить константность. Пример, показываемый мной ниже, из-за добавления const скомпилирован не будет.
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
usingnamespacestd;
template<typenameT>
voidfoo(T*value){(*value)++;}
intmain()
{
int*value=newint(0);
foo(const_cast<constint*>(value));//<-- приведение с добавлением const
//<-- к не const оригиналу
}
Операцию const_cast для сброса const следует использовать только тогда, когда точно знаешь, что делаешь. Использование её нами в нашем коде равнозначно отмашке от компилятора: "Скройся! Я лучше тебя знаю, что нужно делать."
Поэтому лучше всего это приведение ради сброса const не использовать никогда, только если выхода никакого нет, и всё сто тысяч пятьсот раз хорошенько обдумано, только тогда её имеет смысл использовать с целью сброса константности; на своё страх и риск, с осознанием того, что это очень и очень плохо.
А вот использование операции const_cast ради добавления константности — хорошо. Константы всегда хорошо.
Из Стандарта С++
Некоторые преобразования, которые включают только изменения в cv-квалификациях, не могут быть выполнены с помощью операции const_cast. Например, преобразования между указателями на функции не покрываются этой операцией, так как такие преобразования приводят к значениям, использование которых вызывает неопределенное поведение программы. По тем же причинам преобразования между указателями на функции-члены и, в частности, преобразование от указателя на константную функцию-член к указателю на неконстантную функцию-член этой операцией не покрывается.
Подводим некоторые итоги:
Операция const_cast — это операция снятия константности или добавления константности при преобразовании типа.
На самом деле при любом преобразовании переменная, которую приводят к какому-то типу, остаётся той переменной, какой она была. На основе оригинальной переменной создаётся временная переменная, тип для которой подхватывается из типа оригинальной переменной. Вот отбрасыванием const или добавлением const к типу временной переменной операция const_cast и занимается.
Операция const_cast позволяет выполнить приведение типа только если значение объявлено как const или volatile.
const_cast возможно использовать, если тип, к которому выражение приводится, такой же, как у самого выражения, не учитывая квалификаторы const/volatile.
Т. е. типы внутри угловых скобок и внутри круглых должны быть одинаковыми (если устно отбросить квалификаторы const и volatile.
Добавить комментарий