Когда использовать reinterpret_cast? - PullRequest
401 голосов
/ 21 февраля 2009

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

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

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

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

Ответы [ 10 ]

388 голосов
/ 21 февраля 2009

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

static_cast указатель на и из 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 предпочтительнее.

138 голосов
/ 21 февраля 2009

Один случай, когда необходимо 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; }
76 голосов
/ 07 апреля 2017

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

Полный ответ:

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

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

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

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

/*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, независимо от того, какой порядковый номер используется.

EDIT:

В первой версии я сделал пример функции is_little_endian равной constexpr. Он прекрасно компилируется на новейшем gcc (8.3.0), но стандарт говорит, что это незаконно. Компилятор clang отказывается компилировать его (что правильно).

18 голосов
/ 21 февраля 2009

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

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

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

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

11 голосов
/ 12 января 2016

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

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, но аналогичным приведением C ++ является reinterpret_cast.

3 голосов
/ 29 октября 2014

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

1 голос
/ 23 февраля 2017

Сначала у вас есть данные определенного типа, например, 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

КРАТКОЕ ОПИСАНИЕ: это означает, что одна и та же память используется в качестве другого типа. Таким образом, вы можете преобразовать двоичные представления типов с плавающей точкой, как указано выше, в типы с плавающей точкой. Например, 0x80000000 равно -0 (мантисса и показатель степени равны нулю, но знак msb равен единице. Это также работает для двойных и длинных двойных.

ОПТИМИЗАЦИЯ: Я думаю, что reinterpret_cast будет оптимизирован во многих компиляторах, тогда как c-приведение выполняется с помощью pointerarithmetic (значение должно быть скопировано в память, потому что указатели не могут указывать на cpu-регистры).

ПРИМЕЧАНИЕ: в обоих случаях вы должны сохранить приведенное значение в переменной перед приведением! Этот макрос может помочь:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
1 голос
/ 18 января 2013
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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

0 голосов
/ 10 ноября 2016

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

0 голосов
/ 21 февраля 2009

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...