Перейти к содержимому

Const cast c как использовать

  • автор:

Оператор 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 (expression) – это прямое указание компилятору обращаться с некоторой последовательностью битов, являющихся результатом выражения ( expression ), так будто это объект типа new_type . Можно, например, привести целое к указателю, или один указатель к иному произвольному указателю. Его стоит использовать для приведения несовместимых типов, но при этом надо учитывать, что всегда есть вероятность, что такой код перестанет работать на другой платформе. Рассмотрим несколько примеров. Предположим, что имеется две сторонних функции. Одна обеспечивает формирование исходного массива данных, а вторая используется для его пост-обработки. Однако первая работает с char* , а вторая с unsigned char* :

// Функция, формирующая исходный массив данных 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++

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *