10

В C++ существует оператор reinterpret_cast, смысл которого заключается в приведении между типами, несовместимыми друг с другом.

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

В чём же тогда заключается смысл существования данного оператора, если его использование нарушает стандарт?

Arhadthedev
  • 11,528
  • 2
    Не забивайте себе голову ерундой, везде используйте старый сишный cast --(TYPE)(expession) (конечно, помнить (и проверять на всех платформах) о strict-alasing (или отключать его при компиляции файла) все рауно придется) – avp Sep 09 '17 at 09:20
  • 4
    @avp используйте старый сишный cast — зачем использовать инструмент, у которого нет никаких гарантий, если есть железобетонно работающее решение (тот же memcpy, к которому, кстати, так и хочется приписать static_assert с проверкой на равенство размеров типов). помнить ... все равно придется — невозможно предсказать, как поведёт себя оптимизатор при смене версии компилятора на более новую. Есть стандарт, и есть даваемые им гарантии, делающие язык платформонезависимым. – Arhadthedev Sep 09 '17 at 09:37
  • Все приведения типов вещь довольно тонкая и всегда приходится разбираться, осознано ли программист делает такое приведение типа или в данном месте он просто борется с языком. И разнообразие кастов не упрощает эту задачу. – avp Sep 09 '17 at 09:50
  • @avp, разнообразие ключевых слов для преобразования облегчает поиск оных в коде. Т.е. если хочется найти все определённые преобразования, то это очень легко сделать. Поэтому их использование оправдано. – ixSci Sep 09 '17 at 10:53
  • 4
    @avp, не надо учить людей таким глупостям. Сишный каст слишком общий, и этим он провоцирует ошибки. Если достаточно const_cast то, именно его и нужно использовать. – yrHeTateJlb Sep 09 '17 at 19:34
  • 1
    @yrHeTaTeJlb, это не глупости, это реальная жизнь, а не академические советы – avp Sep 09 '17 at 19:48
  • 3
    @avp, это реальная жизнь программиста на С, который на скорую руку переучился на С++. Даже у компиляторов и статических анализаторов есть диагностика "warning: old style cast". Уж это что-то да значит, вы так не считаете? – yrHeTateJlb Sep 09 '17 at 21:16
  • @yrHeTaTeJlb, это реалии модификации кода у авторов которого уже не спросишь, что именно они имели в виду... Что же касается диагностики, а что вы хотите от апологетов крестов? – avp Sep 09 '17 at 21:28
  • 3
    @avp, если вы не умеете модифицировать код иначе, я ничего не имею против. Просто прошу не учить этому других. С-каст это механизм, который позволяет выстрелить себе в ногу тысячей и одним способом. C++ касты куда более безопасные. – yrHeTateJlb Sep 09 '17 at 21:44
  • @yrHeTaTeJlb, вы так и не поняли, что проблема в том, чтобы выяснить мотивы программиста, использующего в конкретном месте тот или иной каст (вы же не ожидаете, что все они пишут без ошибок?) – avp Sep 09 '17 at 22:02

3 Answers3

13

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.

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

αλεχολυτ
  • 28,987
  • 13
  • 60
  • 119
4

Существует ровно один вид преобразования между несовместимыми типами, не нарушающий strict aliasing rule — из произвольного указателя на указатель типа char*. То есть reinterpret_cast позволяет представить произвольный объект в виде последовательности байт (так как стандартом гарантируется однобайтовая длина char-а).

Вот пример грамотного использования этого вида преобразования:

template<class T>
void putIntoStream(const T* object, std::ostream& out)
{
    out.write(reinterpret_cast<const char*>(object), sizeof(T));
}

Для всего остального есть memcpy().

Arhadthedev
  • 11,528
  • 5
    Для такого неполного ответа он у Вас чересчур категоричен. Преобразование не даёт UB ни в каких случаях, UB может последовать если использовать результат. Более того, reinterpret_cast используется не только для преобразования указателей, но и для конвертирования целочисленных адресов в указатели и обратно. – ixSci Sep 09 '17 at 10:55
  • @ixSci, Стандарт явно запрещает преобразование, после которого в указателе оказывается адрес, который нарушает требования выравнивания или псевдонимов. Разыменовывать его вовсе не обязательно. – MGNeo Jul 14 '20 at 13:54
  • @MGNeo цитату? Strict aliasing и alignment никак не могут быть нарушены, пока не происходит обращения по указателю/ссылке. – ixSci Jul 14 '20 at 17:39
2

Несмотря на то, что использование reinterpret_cast в большинстве случаев приводит к неопределенному поведению, библиотеки могут использовать его в своих реализациях, тестируя их для конкретных компиляторов. Для пользователя библиотек поведение уже не будет неопределённым, т.к. оно проверено и документировано, но ему придётся учитывать список поддерживаемых компиляторов. Иногда это единственный способ разработки кросплатформенной библиотеки.

Кроме того, всё еще есть случаи, когда приходится жертвовать переносимостью для достижения других целей (в частности, это актуально для микроконтроллеров).

diralik
  • 9,395
Ariox
  • 2,990