Когда использовать reinterpret cast?

я немного смущен применимостью reinterpret_cast vs static_cast. Из того, что я прочитал, общие правила заключаются в использовании статического приведения, когда типы могут быть интерпретированы во время компиляции, следовательно, слово static. Это приведение компилятор C++ также использует внутренне для неявных приведений.

reinterpret_casts применимы в двух сценариях: преобразование целочисленных типов в типы указателей и наоборот или преобразование одного типа указателя в другой. Общую идею я вам это непереносимых и его следует избегать.

где я немного смущен, это одно использование, которое мне нужно, я вызываю C++ из C, и код C должен держаться за объект C++, поэтому в основном он содержит void*. Какой бросок следует использовать для преобразования между void * а тип класса?

Я видел использование как static_cast и reinterpret_cast? Хотя из того, что я читал, это кажется static лучше, поскольку приведение может произойти во время компиляции? Хотя он говорит использовать reinterpret_cast to преобразования из одного типа указателя к другому?

10 ответов


стандарт C++ гарантирует следующее:

static_casting указатель на и из void* сохраняет адрес. То есть, в следующем, a, b и c все указывают на один и тот же адрес:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_cast только гарантирует, что если вы наведете указатель на другой тип,а то reinterpret_cast он возвращается к исходному типу, вы получаете первоначальное значение. Итак, в следующем:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

a и c содержат одно и то же значение, но значение b является неуказанным. (на практике он обычно содержит тот же адрес, что и a и c, но это не указано в стандарте, и это может быть неверно на машинах с более сложными системами памяти.)

для литья в и из пустоты*,static_cast следует отдать предпочтение.


один случай, когда reinterpret_cast - это необходимо при взаимодействии с непрозрачными типами данных. Это часто происходит в API поставщиков, над которыми программист не имеет никакого контроля. Вот надуманный пример, когда поставщик предоставляет API для хранения и извлечения произвольных глобальных данных:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

чтобы использовать этот API, программист должен привести свои данные к VendorGlobalUserData и обратно. static_cast не будет работать, нужно использовать reinterpret_cast:

// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;

struct MyUserData {
    MyUserData() : m(42) {}
    int m;
};

int main() {
    MyUserData u;

        // store global data
    VendorGlobalUserData d1;
//  d1 = &u;                                          // compile error
//  d1 = static_cast<VendorGlobalUserData>(&u);       // compile error
    d1 = reinterpret_cast<VendorGlobalUserData>(&u);  // ok
    VendorSetUserData(d1);

        // do other stuff...

        // retrieve global data
    VendorGlobalUserData d2 = VendorGetUserData();
    MyUserData * p = 0;
//  p = d2;                                           // compile error
//  p = static_cast<MyUserData *>(d2);                // compile error
    p = reinterpret_cast<MyUserData *>(d2);           // ok

    if (p) { cout << p->m << endl; }
    return 0;
}

Ниже приведена надуманная реализация пример API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }

короткий ответ: Если вы не знаете, что reinterpret_cast означает, не используйте его. Если вам это понадобится в будущем, вы узнаете.

ответ:

давайте рассмотрим основные типы чисел.

при преобразовании, например int(12) to unsigned float (12.0f) ваш процессор должен вызвать некоторые вычисления, поскольку оба числа имеют различное битовое представление. Это static_cast стоит для.

С другой стороны, когда ты называешь reinterpret_cast CPU не вызывает никаких вычислений. Он просто обрабатывает набор битов в памяти, как если бы у него был другой тип. Поэтому, когда вы конвертируете int* to float* С этим ключевым словом новое значение (после разыменования указателя) не имеет ничего общего со старым значением в математическом смысле.

пример: это правда, что reinterpret_cast не переносится по одной причине-байт порядка (порядок байтов). Но это часто удивительно лучшая причина, чтобы использовать его. Давайте представим пример: вы должны прочитать двоичное 32-битное число из файла, и вы знаете, что это большой endian. Ваш код должен быть универсальным и правильно работать в системах big endian (например, ARM) и little endian (например, x86). Поэтому вы должны проверить порядок байтов. Он хорошо известен во время компиляции, поэтому вы можете написать constexpr функция:

constexpr bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

объяснение: бинарный представление x в памяти может быть 0000'0000'0000'0001 (большой) или 0000'0001'0000'0000 (прямой порядок байтов). После переинтерпретации-литье байта под p указатель может быть соответственно 0000'0000 или 0000'0001. Если вы используете статическое литье, оно всегда будет 0000'0001, независимо от того, какая endianness используется.


смысл reinterpret_cast не определяется стандартом C++. Следовательно, теоретически a reinterpret_cast может привести к сбою программы. На практике компиляторы пытаются делать то, что вы ожидаете, а именно интерпретировать биты того, что вы передаете, как если бы они были типом, к которому вы обращаетесь. Если вы знаете, что компиляторы, которые вы собираетесь использовать делать reinterpret_cast вы можете использовать его, но сказать, что это портативный было бы ложью.

для случая, который вы описываете, и почти в любом случае где вы могли бы рассмотреть reinterpret_cast, вы можете использовать static_cast или какая-то другая альтернатива. Среди прочего, стандарт говорит о том, что вы можете ожидать static_cast (§5.2.9):

rvalue типа "указатель на cv void" может быть явно преобразован в указатель на тип объекта. Значение типа указатель на объект, преобразованное в" указатель на cv void " и обратно к исходному типу указателя, будет иметь исходное значение.

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


одно использование reinterpret_cast - если вы хотите применить побитовые операции к (IEEE 754) поплавкам. Одним из примеров этого был быстрый трюк с обратным квадратным корнем:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

он рассматривает двоичное представление поплавка как целое число, сдвигает его вправо и вычитает его из константы, тем самым вдвое уменьшая и отрицая показатель. После преобразования обратно в поплавок, он подвергается итерация Ньютона-Рафсона, чтобы сделать это приближение более точным:

float Q_rsqrt( float number )
{
    long i;
    float x2, y;
    const float threehalfs = 1.5F;

    x2 = number * 0.5F;
    y  = number;
    i  = * ( long * ) &y;                       // evil floating point bit level hacking
    i  = 0x5f3759df - ( i >> 1 );               // what the deuce? 
    y  = * ( float * ) &i;
    y  = y * ( threehalfs - ( x2 * y * y ) );   // 1st iteration
//  y  = y * ( threehalfs - ( x2 * y * y ) );   // 2nd iteration, this can be removed

    return y;
}

Это было первоначально написано на C, поэтому использует C casts, но аналогичный c++ cast является reinterpret_cast.


вы можете использовать reinterprete_cast для проверки наследования во время компиляции.
Смотреть здесь: использование reinterpret_cast для проверки наследования во время компиляции


template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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


сначала у вас есть некоторые данные в определенном типе, как int здесь:

int x = 0x7fffffff://==nan in binary representation

затем вы хотите получить доступ к той же переменной, что и другой тип, например float: Вы можете выбрать между

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

или

float y = *(float*)&(x);

//this could be used in c and cpp

BRIEF: это означает, что одна и та же память используется как другой тип. Таким образом, вы можете конвертировать двоичные представления поплавков как тип int, как указано выше, в поплавки. Например, 0x80000000 -0 (мантисса и Экспонента равны нулю, но знак, msb, один. Это также работает для двойников и длинных двойников.

OPTIMIZE: я думаю, reinterpret_cast будет оптимизирован во многих компиляторах, в то время как C-литье производится pointerarithmetic (значение должно быть скопировано в память, потому что указатели не могут указывать на cpu - регистры).

примечание: в обоих случаях вы должны сохранить приведенное значение в переменной перед приведением! Этот макрос может помочь:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

быстрый ответ: используйте static_cast если он компилируется, в противном случае прибегайте к reinterpret_cast.


читать часто задаваемые вопросы! Хранение данных C++ в C может быть рискованным.

в C++ указатель на объект может быть преобразован в void * без каких-либо повязок. Но с другой стороны, это неправда. Вам понадобится static_cast чтобы вернуть исходный указатель.