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

Reinterpret cast c что это

  • автор:

Синтаксис: приведение типов данных в C и C++

Cи-стиль приведения типов данных доступен и языке C++, но считается не самодостаточным по сравнению с приведением типов в C++. Так как Си-стиль приведения типов не так точен, как C++-стиль приведения и не так заметен. Cи-стиль приведения типов данных может быть использован для преобразования любого типа в любой другой тип, при этом неважно насколько это небезопасное преобразование, например, преобразование целого числа в указатель типа int ). Казалось бы, такое преобразование невозможно, однако компилятор с нами не согласен, но выполнит это приведение. И каков получится результат. ему совершенно не важно. Смотрим синтаксис приведения типов данных согласно Си-стилю:

(type) val
  • type — тип данных к которому преобразуется значение val

причем тип данных обязательно указывается в круглых скобочках.

Рассмотрим следующий пример использования Си-стиля приведения типа данных int к типу double .

double res = (double)13 / 7;

Этот пример приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления. Если вы плохо знакомы с приведением типов данных, прочтите статью: Явное и неявное приведение типов данных.

static_cast — унарная операция приведения типов данных в С++

Операция static_cast доступна только в языке C++. static_cast может быть использована для преобразования одного типа в другой, но она не должна быть использована для выполнения недопустимого преобразования, например, преобразование значения в указатель или наоборот. Рекомендуется пользоваться операцией static_cast , нежели Cи-стилем приведения, потому что static_cast ограничивает недопустимое приведение типов и, следовательно — безопаснее.

Операция static_cast , грубо говоря, — это шаблон функции, в которой необходимо явно указать тип данных для преобразования, то есть задать параметр шаблона.

static_cast(value);

Итак, в треугольных скобочках указывается тип данных, к которому необходимо преобразовать значение value , которое стоит в круглых скобочках. Смотрим все тот же пример, который приводит int к типу double для того, чтобы при делении 13-ти на 7 избежать усечения результата из-за целочисленного деления.

double res = static_cast(13)/7;
dynamic_cast — унарная операция приведения типов данных в С++

Операция dynamic_cast доступна только в C++ и имеет смысл только, применительно к членам класса иерархии «полиморфных типов». Динамическое приведение типов данных может быть использовано для безопасного приведения указателя (или ссылки) на суперкласс, в указатель (или ссылку) на подкласс в иерархии классов. Если динамическое приведение типов — недопустимо, так как реальный тип объекта, указывает не на тот тип подкласса, приведение типов не выполнится.

Динамическое приведение указателя

При приведении указателя, в случае неудачи, dynamic_cast возвращает нулевой указатель NULL . Такое поведение обеспечивает быстрый способ определения, является ли данный объект частностью динамического типа.

Синтаксис указателя динамического приведения:

type *subСlass = dynamic_cast( objPtr );
Динамическое приведение ссылки

При приведении ссылочной переменной, не возможно вернуть указатель, в случае неудачи. Поэтому будет вызвано исключение std::bad_cast (из заголовочного файла ).

type subСlass = dynamic_cast( objReference );

Чтобы безопасно пользоваться динамическим приведением, все вызовы dynamic_cast должны быть обрамлены в блок Try/Catch .

const_cast — константное приведение типов данных

Операция const_cast доступна только в C++. Константное приведение используются, чтобы константную переменную преобразовать в неконстантную. При этом, константным становится возвращаемое значение операции const_cast , а не сама переменная.

const_cast(val);

Следующий пример преобразует константный указатель на символьную строку в неконстантный указатель на эту же строку.

void function(char *); // прототип функции с неконстантным параметром const char *string = "Sevastopol"; // константная строка function(const_cast(string));
reinterpret_cast — операция приведения типов данных

Операция reinterpret_cast доступна только в C++ и является наименее безопасной формой приведения типов данных в С++, она позволяет интерпретировать значение в другой тип данных. reinterpret_cast не должна быть использована для приведения иерархии классов или преобразования константных переменных.

reinterpret_cast( value );

Рассмотрим пример использования этой операции приведения, например, чтобы преобразовать целое значение в указатель, нужно написать следующее:

reinterpret_cast(777);

Более подробно о приведении типов данных читайте в статье: Явное и неявное преобразование типов данных.

Приведение типов

Будучи на конференции Qt Developer Days 2010 я узнал, что одним из самых популярных вопросов на собеседовании в разные зарубежные компании, работающие с Qt библиотекой, является вопрос о различиях в способах приведения типов в C++. Поэтому здесь я рассмотрю основные различия между static_cast, dynamic_cast, const_cast, reinterpret_cast, C-style cast, qobject_cast и qvariant_cast

1. static_cast.

TYPE static_cast (object);

static_cast преобразует выражения одного статического типа в объекты и значения другого статического типа. Поддерживается преобразование численных типов, указателей и ссылок по иерархии наследования как вверх, так и вниз. Проверка производится на уровне компиляции, так что в случае ошибки сообщение будет получено в момент сборки приложения или библиотеки.

2. dynamic_cast

TYPE& dynamic_cast (object); TYPE* dynamic_cast (object);

Используется для динамического приведения типов во время выполнения. В случае неправильного приведения типов для ссылок вызывается исключительная ситуация std::bad_cast, а для указателей будет возвращен 0. Использует систему RTTI (Runtime Type Information). Безопасное приведение типов по иерархии наследования, в том числе для виртуального наследования.

3. const_cast.

TYPE const_cast (object);

Пожалуй самое простое приведение типов. Снимает cv qualifiers — const и volatile, то есть константность и отказ от оптимизации компилятором переменной. Это преобразование проверяется на уровне компиляции и в случае ошибки приведения типов будет выдано сообщение.

4. reinterpret_cast

TYPE reinterpret_cast (object);

Приведение типов без проверки. reinterpret_cast — непосредственное указание компилятору. Применяется только в случае полной уверенности программиста в собственных действиях. Не снимает константность и volatile. применяется для приведения указателя к указателю, указателя к целому и наоборот.

5. C-style cast

TYPE (TYPE*) object;

Си-шный метод приведения типов. Пожалуй самый нежелательный способ приведения типов. Страуструп пишет:
«Например, что это значит выражение — x = (T)y;. Мы не знаем. Это зависит от типа T, типов x и y. T может быть названием типа, typedef или может быть параметр template-а. Может быть, х и у являются скалярными переменными и Т представляет собой значение преобразования. Может быть, х объекта класса, производного от класса Y и Т — нисходящее преобразование. По этой причине программист может не знать, что он делает на самом деле.»
Вторая причина нежелательного использования приведения типов в C-style — трудоемкость процесса поиска мест приведения типов.

6. qobject_cast

TYPE qobject_cast(QObject *object)

Приводит объект QObject* к типу TYPE если объект типа объекта TYPE или тип наследует от TYPE иначе возвращает 0. qobject_cast от 0 также дает 0. Необходимое условие. Класс должен наследовать от QObject и содержать в себе макрос Q_OBJECT. Функция ведет себя аналогично стандартному dynamic_cast, но при этом не использует RTTI. Вот как описана данная функция в Qt 4.7.0:

  1. template
  2. inline T qobject_cast(QObject * object )
  3. #if !defined(QT_NO_MEMBER_TEMPLATES) && !defined(QT_NO_QOBJECT_CHECK)
  4. reinterpret_cast(0)->qt_check_for_QOBJECT_macro(*reinterpret_cast( object ));
  5. #endif
  6. return static_cast(reinterpret_cast(0)->staticMetaObject.cast( object ));
  7. >

Итак, что тут происходит:

Во-первых если не определены QT_NO_MEMBER_TEMPLATES (определяется только в том случае, если используется версия Microsoft Visual Studio ниже 2002) и QT_NO_QOBJECT_CHECK (определяется в случае использования версии Microsoft Visual Studio ниже 2003), то происходит проверка наличия макроса Q_OBJECT в объявлении класса. И после этого выполняется непосредственно само преобразование — сначала получаем статический объект класса QMetaObject, который называется staticMetaObject, у которого вызывается метод cast, который возвращает const_cast переданного ему объекта, попутно проверяя наследуется ли данный объект от QObject. Далее полученному объекту делается static_cast и возвращается результат.

7. qvariant_cast

TYPE qvariant_cast(const QVariant &value)

Приводит объект класса QVariant к нужному классу. Функция аналогична функции qVariantValue.

Рассмотрим, что происходит внутри:

  1. template inline T qvariant_cast( const QVariant &v)
  2. const int vid = qMetaTypeId(static_cast(0));
  3. if (vid == v.userType())
  4. return *reinterpret_cast< const T *>(v.constData());
  5. if (vid < int (QMetaType::User))
  6. T t;
  7. if (qvariant_cast_helper(v, QVariant::Type(vid), &t))
  8. return t;
  9. >
  10. return T();
  11. >

В первой секции кода производится получение идентификатора класса через метасистему Qt. В том случае если класс не зарегистрирован через Q_DECLARE_METATYPE, компиляция кода с приведением к этому типу выдаст ошибку. Далее, если тип объекта, полученный от метасистемы совпадает с типом в значении QVariant, производится reinterpret_cast содержимого объекта, если идентификатор класса не является встроенным типом и его id не совпадает с заложенным в значении QVariant, то возвращается TYPE(). Для случаев, когда мы приводим к встроенному типу, вызывается функция qvariant_cast_helper, которая вызывает в свою очередь функцию convert, адрес которой хранится в структуре Handler. В ней уже осуществляется приведение способом подходящим для типа TYPE. Если конвертация не удалась возвращается объект TYPE()

  • const_cast
  • static_cast
  • static_cast + const_cast
  • reinterpret_cast
  • reinterpret_cast + const_cast

UPD: Спасибо BaJlepa:
1. const_cast также умеет добавлять cv-квалификаторы
2. для преобразования указателей лучше использовать двойной static_cast через void* вместо reinterpret_cast, потому как такое преобразование позволяет быть уверенным в том, что только pointer-ы участвуют в приведении

Еще раз про приведение типов в языке С++ или расстановка всех точек над cast

Этот пост попытка кратко оформить все, что я читал или слышал из разных источников про операторы приведения типов в языке C++. Информация ориентирована в основном на тех, кто изучает C++ относительно недолго и, как мне кажется, должна помочь понять cпецифику применения данных операторов. Старожилы и гуру С++ возможно помогут дополнить или скорректировать описанную мной картину. Всех интересующихся приглашаю под кат.

Приведение типов в стиле языка C (C-style cast)

Приведение типов в стиле языка C может привести выражение любого типа к любому другому типу данных (исключение это приведение пользовательских типов по значению, если не определены правила их приведения, а также приведение вещественного типа к указателю или наоборот). К примеру, unsigned int может быть преобразован к указателю на double. Данный метод приведения типов может быть использован в языке C++. Однако, метод приведения типов в стиле языка C не делает проверки типов на совместимость, как это могут сделать static_cast и dynamic_cast на этапе компиляции и на этапе выполнения соответственно. При этом все, что умеют const_cast и reinterpret_cast данный метод приведения типов делать может.

Общий вид приведения:

(new_type)exp

, где new_type – новый тип, к которому приводим, а exp – выражение, которое приводится к новому типу.

Т.к. данный оператор не имеет зарезервированного ключевого слова (например, static_cast) найти все места приведения типов в тексте программы будет не очень удобно, если это потребуется.

Показать пример

#include // Пустые классы только // для теста приведения struct foo < >; struct bar < >; // Наследники bar struct baz : bar < >; struct quux : bar < >; int main() < // Переменные простых типов и указатели на переменные простых типов int i = 5; double d = 111.222; char c = 'a'; int* pi = &i; double* pd = &d; const int* pсi = &i; void* pv = NULL; // Объекты классов foo oFoo; bar oBar; baz oBaz; quux oQuux; // Указатели на объекты классов foo* pFoo = &oFoo; bar* pBar = &oBar; baz* pBaz = &oBaz; quux* pQuux = &oQuux; // Приводим явно double к int i = (int)d; // и наоборот d = (double)i; // указатель на int к char c = (char)pi; // char к указателю на void pv = (void*)c; // указатель на void к указателю на int pi = (int*)pv; // Снимаем константность const int* pi = (int*)pсi; // Приводим указатель на объект bar к указателю на объект foo // из разных иерархий pFoo = (foo*)pBar; // Приводим указатель на double к double d = (double)*pd; // Ошибка. // А если наоборот? pd = (double*)d; // Ошибка. // Перемещение из одной иерархии наследования в другую pBar = (bar*)pBaz; pQuux = (quux*)pBar; return 0; >
const_cast

Оператор приведения const_cast удаляет или добавляет квалификаторы const и volatile с исходного типа данных (простые типы, пользовательские типы, указатели, ссылки). Например, был const int, а после преобразования стал int или наоборот. Квалификаторы const и volatile называют cv-квалификаторы (cv-qualifiers). Данные квалификаторы указываются перед именами типов. Как ни трудно догадаться квалификатор const задает константность, т.е. защищает переменную от изменения. Квалификатор volatile говорит о том, что значение переменной может меняться без явного выполнения присваивания. Это обеспечивает защиту от оптимизации компилятором операций с данной переменной.

Общий вид приведения:

Показать пример

#include // Снятие константности void foo(const int* pc, const int& r) < int* p; // Сняли константность и записали 33 p = const_cast(pc); *p = 33; // Сняли константность и записали 55 const_cast(r) = 55; > // Добавление константности void bar(int* p, int& r) < const int* pc; // Добавили константность // и пытаемся записать 33 pc= const_cast(p); *pc = 33; // Ошибка. // Добавили константность // и пытаемся записать 55 const_cast(r) = 55; // Ошибка. > // Снятие volatile void baz(volatile int* pv, volatile int& r) < int* p; // Сняли volatile и записали 33 p = const_cast(pv); *p = 33; // Сняли volatile и записали 55 const_cast(r) = 55; > // Добавление volatile void quux(int* p, int& r) < volatile int* pv; // Добавили volatile и записали 33 pv = const_cast(p); *pv = 33; // Добавили volatile и записали 55 const_cast(r) = 55; > int main() < int x = 3, y = 5; std::cout 

Дополнительный пример от пользователя 5nw

Показать пример
#include using namespace std; void f(int* x) < cout void f(const int* x) < cout int main() < int x = 5; int* px = &x; f(px); f(const_cast(px)); return 0; > 

Квалификаторы const и volatile можно удалить или добавить только с помощью оператора приведения const_cast и приведения типов в стиле языка C. Другие операторы приведения типов не влияют на квалификаторы const и volatile (reinterpret_cast, static_cast, dynamic_cast).

reinterpret_cast

Оператор приведения reinterpret_cast используется для приведения несовместимых типов. Может приводить целое число к указателю, указатель к целому числу, указатель к указателю (это же касается и ссылок). Является функционально усеченным аналогом приведения типов в стиле языка С. Отличие состоит в том, что reinterpret_cast не может снимать квалификаторы const и volatile, а также не может делать небезопасное приведение типов не через указатели, а напрямую по значению. Например, переменную типа int к переменной типа double привести при помощи reinterpret_cast нельзя.

Общий вид приведения:

reinterpret_cast(exp)

Показать пример

#include // Пустые классы только для теста приведения struct foo < >; struct bar < >; // Наследники bar struct baz : bar < >; struct quux : bar < >; int main() < // Переменные простых типов и указатели на переменные простых типов int i = 5; double d = 111.222; char c = 'a'; int* pi = &i; double* pd = &d; const int* pсi = &i; void* pv = nullptr; // Объекты классов foo oFoo; bar oBar; baz oBaz; quux oQuux; // Указатели на объекты классов foo* pFoo = &oFoo; bar* pBar = &oBar; baz* pBaz = &oBaz; quux* pQuux = &oQuux; // Приводим явно double к int i = reinterpret_cast(d); // Ошибка. // и наоборот d = reinterpret_cast(i); // Ошибка. // указатель на int к char c = reinterpret_cast(pi); // char к указателю на void pv = reinterpret_cast(c); // указатель на void к указателю на int pi = reinterpret_cast(pv); // Снимаем константность const int* pi = reinterpret_cast(pсi); // Ошибка. // Приводим указатель на объект bar к указателю на объект foo // из разных иерархий pFoo = reinterpret_cast(pBar); // Приводим указатель на double к double d = reinterpret_cast(pd); // Ошибка. // А если наоборот? pd = reinterpret_cast(d); // Ошибка. // Перемещение из одной иерархии наследования в другую pBar = reinterpret_cast(pBaz); pQuux = reinterpret_cast(pQuux); return 0; > 
static_cast

Оператор приведения static_cast применяется для неполиморфного приведения типов на этапе компиляции программы. Отличие static_cast от приведения типов в стиле языка C состоит в том, что данный оператор приведения может отслеживать недопустимые преобразования, такие как приведение указателя к значению или наоборот (unsigned int к указателю на double не приведет), а также приведение указателей и ссылок разных типов считается корректным только, если это приведение вверх или вниз по одной иерархии наследования классов, либо это указатель на void. В случае фиксации отклонения от данных ограничений будет выдана ошибка при компиляции программы. При множественном наследовании static_cast может вернуть указатель не на исходный объект, а на его подобъект.

Общий вид приведения:

Показать пример

#include // Пустые классы только для теста приведения struct foo < >; struct bar < >; // Наследники bar struct baz : bar < >; struct quux : bar < >; int main() < // Переменные простых типов и указатели на переменные простых типов int i = 5; double d = 111.222; char c = 'a'; int* pi = &i; double* pd = &d; const int* pci = &i; void* pv = nullptr; // Объекты классов foo oFoo; bar oBar; baz oBaz; quux oQuux; // Указатели на объекты классов foo* pFoo = &oFoo; bar* pBar = &oBar; baz* pBaz = &oBaz; quux* pQuux = &oQuux; // Приводим явно double к int i = static_cast(d); // и наоборот d = static_cast(i); // указатель на int к char c = static_cast(*pi); // Ошибка. // char к указателю на void pv = static_cast(&c); // Ошибка. // указатель на void к указателю на int pi = static_cast(pv); // Снимаем константность const int* pi = static_cast(pci); // Ошибка. // Приводим указатель на объект bar к указателю на объект foo // из разных иерархий pFoo = static_cast(pBar); // Ошибка. // Приводим указатель на double к double d = static_cast(*pd); // Ошибка. // А если наоборот? pd = static_cast(d); // Ошибка. // Перемещение из одной иерархии наследования в другую pBar = static_cast(pBaz); pQuux = static_cast(pBar); return 0; > 
dynamic_cast

Оператор приведения dynamic_cast применяется для полиморфного приведения типов на этапе выполнения программы (класс считается полиморфным, если в нем есть хотя бы одна виртуальная функция). Если указатель, подлежащий приведению, ссылается на объект результирующего класса или объект класса производный от результирующего то приведение считается успешным. То же самое для ссылок. Если приведение невозможно, то на этапе выполнения программы будет возвращен NULL, если приводятся указатели. Если приведение производится над ссылками, то будет сгенерировано исключение std::bad_cast. Несмотря на то, что dynamic_cast предназначен для приведения полиморфных типов по иерархии наследования, он может быть использован и для обычных неполиморфных типов вверх по иерархии. В этом случае ошибка будет получена на этапе компиляции. Оператор приведения dynamic_cast может приводить указатель на полиморфный тип к указателю на void, но не может приводить указатель на void к другому типу. Способность dynamic_cast приводить полиморфные типы обеспечивается системой RTTI (Run-Time Type Identification), которая позволяет идентифицировать тип объекта в процессе выполнения программы. При множественном наследовании dynamic_cast может вернуть указатель не на исходный объект, а на его подобъект.

Общий вид приведения:

Показать пример

#include // Пустые классы только // для теста приведения struct foo < // Сделали полиморфным virtual void do_some() <>; >; struct bar < // Сделали полиморфным virtual void do_some() <>; >; // Наследники bar struct baz : bar < >; struct quux : bar < >; int main() < // Переменные простых типов и указатели на переменные простых типов void* pv = nullptr; // Объекты классов foo oFoo; bar oBar; baz oBaz; quux oQuux; // Указатели на объекты классов foo* pFoo = &oFoo; bar* pBar = &oBar; baz* pBaz = &oBaz; quux* pQuux = &oQuux; // Приводим указатель на объект bar к указателю на объект foo // из разных иерархий pFoo = dynamic_cast(pBar); if (pFoo == nullptr) < std::cout // Приводим указатель на void к указателю на объект bar pBar = dynamic_cast(pv); // Ошибка на этапе компиляции. // Приводим указатель на bar к указателю на void pv = dynamic_cast(pBar); // Перемещение из одной иерархии наследования в другую pBar = dynamic_cast(pBaz); pQuux = dynamic_cast(pBar); if (pQuux == nullptr) < std::cout return 0; > 

В чём смысл существования reinterpret_cast?

В C++ существует оператор reinterpret_cast , смысл которого заключается в приведении между типами, несовместимыми друг с другом. Однако подобные преобразования нарушают strict aliasing rule, что провоцирует неопределённое поведение. Те же преобразования, которые этого правила не нарушают, укладываются в const_cast , static_cast и dynamic_cast . В чём же тогда заключается смысл существования данного оператора, если его использование нарушает стандарт?

  • c++
  • типы-данных
  • language-lawyer
  • неопределённое-поведение

Отслеживать
Arhadthedev
задан 9 сен 2017 в 8:22
Arhadthedev Arhadthedev
11.5k 8 8 золотых знаков 42 42 серебряных знака 69 69 бронзовых знаков

Не забивайте себе голову ерундой, везде используйте старый сишный cast -- (TYPE)(expession) (конечно, помнить (и проверять на всех платформах) о strict-alasing (или отключать его при компиляции файла) все рауно придется)

9 сен 2017 в 9:20

@avp используйте старый сишный cast — зачем использовать инструмент, у которого нет никаких гарантий, если есть железобетонно работающее решение (тот же memcpy , к которому, кстати, так и хочется приписать static_assert с проверкой на равенство размеров типов). помнить . все равно придется — невозможно предсказать, как поведёт себя оптимизатор при смене версии компилятора на более новую. Есть стандарт, и есть даваемые им гарантии, делающие язык платформонезависимым.

9 сен 2017 в 9:37

@avp, не надо учить людей таким глупостям. Сишный каст слишком общий, и этим он провоцирует ошибки. Если достаточно const_cast то, именно его и нужно использовать.

9 сен 2017 в 19:34

@avp, это реальная жизнь программиста на С, который на скорую руку переучился на С++. Даже у компиляторов и статических анализаторов есть диагностика "warning: old style cast". Уж это что-то да значит, вы так не считаете?

9 сен 2017 в 21:16

@avp, если вы не умеете модифицировать код иначе, я ничего не имею против. Просто прошу не учить этому других. С-каст это механизм, который позволяет выстрелить себе в ногу тысячей и одним способом. C++ касты куда более безопасные.

9 сен 2017 в 21:44

3 ответа 3

Сортировка: Сброс на вариант по умолчанию

reinterpret_cast используется не только для преобразования указателей одного типа в другой. Существует несколько разных преобразований. cppreference.com выделяет 11 вариантов преобразований:

  1. В свой собственный тип
  2. Указателя в интегральный тип
  3. Интегрального типа в указатель
  4. Типа std::nullptr_t в интегральный тип
  5. Указателя одного типа в указатель другого типа
  6. lvalue одного типа в ссылку на другой тип
  7. Указателя на функцию одного типа в указатель на функцию другого типа
  8. Указателя на функцию в void*
  9. Нулевого указателя любого типа в указатель любого другого типа
  10. rvalue указатель одного типа на функцию-член в указатель другого типа на функцию-член
  11. rvalue указатель члена-данных одного типа в указатель ну другой член-данных другого типа

Type aliasing-правила затрагивают только пункты 5 и 6 и результат может быть безопасно использован (т.е. без нарушения strict-aliasing) в следующих случаях:

  • Результирующий тип есть динамический тип исходного объекта
  • Результирующий тип и динамический тип указывают на одинаковый тип T
  • Результирующий тип есть знаковый или беззнаковый вариант типа исходного объекта
  • Результирующий тип есть агрегатный тип или union , в котором содержится элемент или нестатический член данных, используемый в качестве исходного объекта. Т.е. можно получить указатель на структуру по указателю на её член.
  • Результирующий тип есть базовый класс динамического типа исходного объекта и этот тип является standard-layout классом и не содержит нестатических членов-данных, и результирующий тип - первый базовый класс.
  • Результирующий тип есть указатель на char , unsigned char или std::byte .

Некоторые реализации ослабляют эти правила в качестве нестандартных расширений языка.

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

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