Оператор const_cast
Удаляет атрибуты const volatile и __unaligned атрибуты из класса.
Синтаксис
const_cast (expression)
Замечания
Указатель на любой тип объекта или указатель на член данных можно явно преобразовать в тип, идентичный, за исключением const volatile квалификаторов, и __unaligned квалификаторов. Для указателей и ссылок результат будет указывать на исходный объект. Для указателей на данные-члены результат будет указывать на тот же член, что и исходный указатель (uncast) на данные-член. В зависимости от типа объекта, на который осуществляется ссылка, операция записи с помощью результирующего указателя, ссылки или указателя на данные-член может привести к неопределенному поведению.
Оператор нельзя использовать const_cast для прямого переопределения состояния константы переменной константы.
Оператор const_cast преобразует значение указателя NULL в значение null указателя целевого типа.
Пример
// expre_const_cast_Operator.cpp // compile with: /EHsc #include using namespace std; class CCTest < public: void setNumber( int ); void printNumber() const; private: int number; >; void CCTest::setNumber( int num ) < number = num; >void CCTest::printNumber() const < cout ( this )->number--; cout int main()
В строке, содержащей const_cast тип данных this указателя const CCTest * . Оператор const_cast изменяет тип this данных указателя CCTest * на , что позволяет изменять элемент number . Приведение выполняется только для оставшейся части оператора, в котором оно указано.
Явное приведение типов в C++
Практически всегда необходимость явного приведения типов в C++ указывает на ошибки проектирования. Но из всех правил есть исключения.
Оператор static_cast
Многие библиотеки, поддерживающие функции обратного вызова, позволяют указать сырой указатель void*, который передается при каждом обратном вызове. Такое решение обеспечивает доступ к произвольному классу из функции обратного вызова. Например: void onCallback( void* userData ); Предположим, что в качестве пользовательских данных мы передали указатель на некоторый класс:
class UserClass < // . >; void testCallback( void ( *callback )( void* ), void* userData ) < callback( userData ); >UserClass c; testCallback( onCallback, &c );
Теперь восстановим указатель в функции onCallback() :
void onCallback( void* userData ) < // UserClass* c = userData; Не сработает UserClass* c = static_cast< UserClass* >( userData ); // А вот это нормально >
Других обоснованных случаев использования static_cast в современном C++ практически не существует.
Оператор reinterpret_cast
reinterpret_cast
// Функция, формирующая исходный массив данных void generatorFunction( char**, size_t* ); // Функция для обработки данных void processorFunction( const unsigned char*, size_t ); char* data = NULL; size_t size = 0; someLibFunction( &data, &size ); // someFunction( data, size ); char* в unsigned char* не преобразовывается // someFunction( static_cast< const unsigned char* >( data ), size ); Слишком разные типы данных someFunction( reinterpret_cast< unsigned char* >( data ), size ); // ОК
Другой пример из области системного программирования. Если аппаратная среда выполнения однозначно определена, то могут быть сделаны допущения относительно распределения памяти:
class SystemDevice; // 0xaabbcc - некий адрес // SystemDevice* device = 0xaabbcc; Недопустимо SystemDevice* device = reinterpret_cast< SystemDevice* >( 0xaabbcc ); // ОК, но может привести к ошибкам
“В общем случае, результат операции reinterpret_cast гарантировано приемлем для использования лишь тогда, когда преобразуемое значение соответствует целевому типу” (Страуструп). Особенно коварны случаи использования reinterpret_cast при наследовании классов. Небольшой пример:
#include struct A < int a; >; struct B < int b; >; struct C : public A, public B < >; int main() < C c; c.a = 1; c.b = 2; std::cout (&c)->b BAD! std::cout (&c)->b OK! >
В этом примере корректно отработал только static_cast . Хотя reinterpret_cast и выдал результат (программа собралась и успешно выполнилась), но этот результат далеко не то, что было нужно нам. Дело в том, что static_cast осуществляет правильную работу с адресами, в то время как reinterpret_cast просто интерпретирует указатель, так как “приказывает” программист, не меняя его значения. Именно этим и опасен reinterpret_cast , он “всё воспринимает на веру”, не проверяя, что мы хотим на самом деле.
Оператор dynamic_cast
Полиморфизм в C++ и других языках программирования обеспечивает свободную навигацию по иерархии классов сверху-вниз от наследников к предкам. Но в редких случаях может потребоваться выполнить обратную процедуру, то есть перейти по известному предку к наследнику:
class A < public: virtual ~A() < >>; class B : public A < >; B* b = new B; // Создаем экземпляр наследника A* a = b; // От наследника к предку: все хорошо // B* anotherB = a; Но от предка к наследнику уже не работает if( B* anotherB = dynamic_cast< B* >( a ) ) < // Допустимо, но не лучший вариант // . >delete b;
Несмотря на то, что в C++ есть dynamic_cast , его использование с большой вероятностью указывает на ошибку в архитектуре приложения, если это не является осознанным проектным решением.
Оператор const_cast
Самый спорный из рассмотренных здесь операторов приведения. Он позволяет снять модификаторы const и volatile с указателей и ссылок объектов. Рассмотрим пример:
void someFunction( const int& x ) < // x = 3; Нельзя: x - константная ссылка const_cast< int& >( x ) = 3; > int x = 5; someFunction2( x ); coutПотребность в использовании const_cast может появиться лишь в случае использования значительного объема стороннего кода, который нельзя изменить. И даже в этом случае это можно считать серьезной ошибкой проектирования.
Приведение типов в стиле Си
(new_type) expression – прямое указание компилятору, что с expression в этои выражении надо работать как с объектом типа new_type . То есть это очень похоже на reinterpret_cast , поэтому описывая эту конструкцию будем постоянно думать о reinterpret_cast и сравнивать. Начнем с примера, выполняющего ту же операцию что reinterpret_cast выше:
#include struct A < int a; >; struct B < int b; >; struct C : public A, public B < >; int main() < C c; c.a = 1; c.b = 2; std::cout b OK! std::cout (&c)->b OK! >Всё отработало так, как и положено: приведение типов в стиле С справилось с поставленной задачей, а reinterpret_cast – нет (показано выше). Но почему? Случайность? А если нет, то как тогда можно утверждать, что это одно и тоже с reinterpret_cast . Вот еще один пример:
#include int main() < int i = 1; const int* p = &i; std::cout (p))Этот пример даст нам ошибку на этапе компиляции:
reinterpret_cast from ‘const int *’ to ‘int *’ casts away qualifiers
Получается, что у приведения типов в стиле С нет проблем справиться с указателем на константу. А вот reinterpret_cast в той же ситуации ”подвёл”, и не справился с задачей. Впрочем, помочь ему мог бы оператор const_cast .
int main() < int i = 1; const int* p = &i; std::cout (const_cast(p))); // OK! >
- сначала пробуем применить оператор const_cast(expression) . Возможно этого будет достаточно. Если нет, то шаг 2.
- static_cast(expression) . Именно благодаря этому шагу, приведение в стиле С отработало правильно, а reinterpret_cast – нет, в приведенных примерах.
- А теперь комбинация из static_cast , который следует за const_cast . Если и это не помогло, то…
- reinterpret_cast(expression) . Но как было видно из последних примеров, даже reinterpret_cast не в силах справиться с квалификатором const. А приведение в стиле С, работает всегда – благодаря шагу №5 (последнему).
- reinterpret_cast , который следует за const_cast (последний пример). На этом шаге компилятор уже точно, сделает то, что Вы просите. Чего бы это ни стоило.
Получается, что приведение типов в стиле С – это “оборотень”: в каких-то случаях оно ведет себя как static_cast , в каких-то как reinterpret_cast , а иногда и как const_cast .
Вывод
После этого краткого обзора, у Вас могло появиться стойкое желание использовать везде “старое доброе приведение” в стиле С, и пусть компилятор сам разберется, вместо того, чтобы случайно “посадить” где-то ошибку с reinterpret_cast . Но это не совсем правильный вывод.
Операторы приведения типов позволят другим программистам лучше осознать намерения Ваших действий. Вы сами скажите себе “спасибо” через некоторое время. Правильный вывод: проектировать так, чтобы не использовать приведение типов вообще, а если и применять, то только то, что действительно нужно. “Ювелирные” приведения часто необходимы, но если начать всюду стрелять из пушки под названием C-style cast, то можно случайно попасть себе же в ногу.
объясните по const_cast
Скажите вот читаю про const_cast. написано const_cast позволяет лишить статуса const или volatile переменную . Но такое ощущение , что не совсем ведь так буквально: потому что рассматривать её как полноценную переменную я не могу :
const int i = 0; const_cast(&i); i = 555;
но могу вот так :
const int i = 0; int* j = const_cast(&i); *j = 555;
то есть , если объявлено const int i = 0;, то после const_cast i можно менять только при помощи указателя ?
Отслеживать
задан 27 сен 2020 в 13:21
Андрей Гуренков Андрей Гуренков
415 3 3 серебряных знака 10 10 бронзовых знаков
Оно изменяет тип выражения, но не изменяет тип исходного объекта. Тип объекта в С/С++ всегда неизменяемый. Соответственно операции, которые можно проводить с результатом любого каста должны учитывать настоящий тип объекта. Попытка модифицировать объект с const квалификатором, как в *j = 555; , является неопределенным поведением.
27 сен 2020 в 13:39
Модификация во втором примере приводит к UB en.cppreference.com/w/cpp/language/const_cast
27 сен 2020 в 13:39
так если "попытка модифицировать объект с const квалификатором, как в *j = 555;, " это неопределенное повдение как пишет user7860670 , то какой смысл от const_cast, если первый мой пример бракует компилятор, а второй это UB ? честно говоря я не могу понять зачем надо константу делать переменной, ну да ладно. как тогда адекватно менять значение у константы после применения const_cast ?
27 сен 2020 в 13:54
@АндрейГуренков смысл const_cast : если возникла необходимость в нём, то понять, что проблема в дизайне/API, и, при возможности, поменять их; либо, если нельзя поменять, использовать const_cast , удостоверившись, что не будет модификации, и рассказав об этом в комментарии
27 сен 2020 в 14:08
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Присваивать объекту, который был определён как const — это в любом случае UB (undefined behavior). const_cast ни коим образом не снимает константность с самого объекта, он только «возвращает» переданное ему значение с нужным типом (это выражение также может быть «присваиваемым» (l-value)).
На практике const_cast используется довольно редко. Обычно это относительно грязный хак, чтобы передать константный объект в функцию, которая заведомо его не меняет (но принимает неконстантную ссылку/указатель) или, наоборот, изменить объект заведомо созданные как неконстантный, но переданный по константной ссылке/указателю. В тривиальном варианте это выглядит как-то так:
void foo (const int &i) < const_cast(i) = 2; > void bar (int *i) < std::cout // . int i=0; const int ci=2; foo(i); bar(const_cast(&ci));
По непосредственному вопросу: с точки зрения синтаксиса можно привести константную переменную к неконстантной ссылке (само собой так делать не стоит):
const int i = 0; const_cast(i) = 2; // !! UB !!
Динамическая идентификация типов. Операторы dynamic_cast , const_cast , reinterpret_cast , static_cast . Примеры
Оператор dynamic_cast используется для динамического приведения типа с одновременной проверкой корректности приведения. Если приведение не может быть выполнено, то оно не выполняется и оператор возвращает нулевое значение ( nullptr ).
Общая форма оператора dynamic_cast следующая
dynamic_casttype> (expression);
- type – результирующий тип;
- expression – выражение, которое приводится к типу type.
Оператор dynamic_cast эффективен в случае приведения типов, образующих иерархию наследования (полимофных типов).
Если оператор dynamic_cast применяется к указателям, возвращается нулевой указатель ( nullptr ). Если этот оператор применяется к ссылкам, в случае ошибки генерируется исключительная ситуация bad_cast .
Пример.
В примере показано использование оператора dynamic_cast для двух классов A и B , которые образуют иеархию наследования. Рассматриваются всевозможные ситуации использования оператора.
#include iostream> #include typeinfo> using namespace std; // Оператор dynamic_cast<> - определяет, можно ли динамически привести типы // Базовый класс class A < public: virtual void Show() < cout "A::Show()" >; // Производный класс class B :public A < public: void Show() < cout "B::Show()" >; void main(void) < // 1. Объявить переменные A* pA; // указатель на базовый класс B* pB; // указатель на производный класс A a; B b; // 2. Использовать указатель pA pA = &a; // Так можно pA->Show(); // Также можно, указатель на базовый класс может указывать на // экземпляр производного класса. pA = &b; pA->Show(); // 3. Использовать указатель pB // Так нельзя // pB = &a; // запрещено, базовый класс не может расширяться до производного // 4. Использование оператора dynamic_cast. // Случай: указатель получает адрес экземпляра класса cout "dynamic_cast(&obj): " // 4.1. Приведение B* pB = dynamic_castB*> (&a); // нельзя, pB = nullptr if (pB==nullptr) cout "Cast A* Error" // этот вариант else cout "Cast A* Ok" // 4.2. Приведение B* pB = dynamic_castB*> (&b); // можно if (pB != nullptr) cout "Cast B* Ok" // этот вариант else cout "Cast B* Error" // 4.3. Приведение A* pA = dynamic_castA*>(&a); // можно, pA!=nullptr if (pA != nullptr) cout "Cast A* Ok" else cout "Cast A* Error"; // 4.4. Приведение A* pA = dynamic_castB*>(&b); // можно if (pA) cout "Cast A* Ok" else cout "Cast A* Error"; cout "---------------------------------" // 5. Использование оператора dynamic_cast. // Случай: указатель получает адрес другого указателя cout "dynamic_cast(ptr -> Type): " // 5.1. Приведение B* pA = &b; // можно, pA->b pB = dynamic_castB*>(pA); // можно, потому что pA ссылается на объект класса B if (pB) cout "Cast B* B) --> Ok " // + else cout "Cast B* B) --> Error " // 5.2. Приведение B* pA = &a; // pA -> A pB = dynamic_castB*>(pA); // нельзя, потому что pA ссылается на объект класса A if (pB) cout "Cast B* A) --> Ok " else cout "Cast B* A) --> Error " // + // 5.3. Приведение A* pB = &b; pA = dynamic_castA*> (pB); // можно if (pA) cout "Cast A* B) --> Ok " else cout "Cast A* B) --> Error" // 5.4. Приведение A* >
Результат выполнения программы
A::Show() B::Show() dynamic_cast(&obj): Cast A* Error Cast B* Ok Cast A* Ok Cast A* Ok --------------------------------- dynamic_cast(ptr -> Type): Cast B* B) --> Ok Cast B* A) --> Error Cast A* B) --> Ok
2. Замена оператора typeid оператором dynamic_cast . Пример
Если классы образуют иерархию (классы полиморфны), то оператор typeid может быть заменен оператором dynamic_cast .
В примере ниже показана эта возможность.
#include iostream> #include typeinfo> using namespace std; // замена оператора typeid оператором dynamic_cast class A < public: virtual void Show() < cout "A::Show()" >; class B :public A < public: void Show() < cout "B::Show()" >; void main(void) < // 1. Объявление указателей и объектов классов A* pA; // указатель на базовый класс B* pB; // указатель на производный класс A a; B b; // 2. Применение оператора typeid cout "typeid:" // 2.1. pA->A pA = &a; if (typeid(*pA) == typeid(B)) // можно ли *pA привести к типу B < pB = (B*)pA; cout "Cast B A) --> OK" else cout "Cast B A) --> Error" // 2.2. pA->B pA = &b; if (typeid(*pA) == typeid(B)) // можно ли *pA привести к типу B < pB = (B*)pA; cout "Cast B B) --> OK" else cout "Cast B B) --> Error" // 3. Применение оператора dynamic_cast cout "-----------------------------------" << endl; cout "dynamic_cast: " // 3.1. pA->A pA = &a; pB = dynamic_castB*> (pA); if (pB) cout "Cast B* A) --> OK" else cout "Cast B* A) --> Error" // 3.2. pA->B pA = &b; pB = dynamic_castB*> (pA); if (pB) cout "Cast B* B) --> OK" else cout "Cast B* Error) --> Error"
Результат выполнения программы
typeid: Cast B A) --> Error Cast B B) --> OK ----------------------------------- dynamic_cast: Cast B* A) --> Error Cast B* B) --> OK
3. Оператор const_cast . Пример
Оператор const_cast используется для замены модификатора const и/или модификатора volatile .
В этом операторе тип результата должен совпадать с исходным. Оператор const_cast применяется для того чтобы избавиться от модификатора const .
Общий вид оператора const_cast следующий
const_cast (expr)
Пример.
С помощью оператора const_cast через ссылку на константную величину можно снять модификатор const с этой константной величины.
#include iostream> using namespace std; // 1. Применение const_cast для ссылки void ExampleConstCast1(const int& value) < // снять спецификатор const, получить доступ к value int& ref = const_castint&> (value); ref = 20; // через ссылку на константную величину можно получить доступ к value // value = 50; - нельзя, expression must be a modifiable lvalue > // 2. Применение const_cast для ссылки void ExampleConstCast2(const int& value) < // снять модификатор const из value const_castint&> (value) = value + value; > // 3. Применение const_cast для указателя void ExampleConstCast3(const int* x) < int* p = const_castint*> (x); // снять const из x *p = 100; > void main() < // Демонстрация использования модификатора const_cast int t = 30; ExampleConstCast1(t); cout "t color: #008000;">// t = 20 ExampleConstCast2(t); cout "t+t color: #008000;">// t = 20+20 = 40 int x = 50; ExampleConstCast3(&x); cout "x color: #008000;">// x = 100 >
Результат выполнения программы
t = 20 t+t = 40 x = 100
4. Оператор reinterpret_cast . Пример
Оператор reinterpret_cast предназначен для преобразования одного типа в другой, даже не совместимый тип. Этот оператор может также применяться к указателям любых типов.
reinterpret_cast (expr)
- type – результирующий тип;
- expr – выражение, которое приводится к новому типу.
Пример.
#include iostream> using namespace std; void main() < // 1. Конвертировать char* => int int number; const char* pStr = "Hello world!"; // получить указатель на str как целое число number = reinterpret_castint> (pStr); cout "number color: #008000;"> // 2. Конвертировать int => double*, // преобразовать целое число в указатель unsigned int num = 300; double* p; p = reinterpret_castdouble*> (num); cout "p color: #333300;">⇑5. Оператор static_cast. Пример
Оператор static_cast используется для выполнения неполиморфного приведения типов без проверки его корректности. Приводимый тип и получаемый тип могут быть такими, что не образуют иерархию наследования.
Общая форма оператора следующая
static_cast (expr)
- type – результирующий тип;
- expr – выражение, приводимое к типу type .
Пример.
#include iostream> using namespace std; void main() < // Применение оператора static_cast - неполиморфное приведение, // используется так же как и обычный оператор приведения // 1. Конвертирование int => double double x; int a = 25; x = static_castdouble> (a) / 4; cout "x color: #008000;">// x = 6.25 // 2. Конвертирование double => char double z = 51.55; char c = static_castchar> (z); // c = '3' cout "c color: #008000;"> // 3. Конвертирование double => short short s = static_castshort>(118.232); cout "s color: #008000;">// s = 118 >
Результат выполнения программы
x = 6.25
c = 3
s = 118
Связанные темы
- Динамическая идентификация типов. Оператор typeid . Примеры
- Базовые типы данных C++