Когда использовать reinterpret cast?
я немного смущен применимостью reinterpret_cast
vs static_cast
. Из того, что я прочитал, общие правила заключаются в использовании статического приведения, когда типы могут быть интерпретированы во время компиляции, следовательно, слово static
. Это приведение компилятор C++ также использует внутренне для неявных приведений.
reinterpret_cast
s применимы в двух сценариях: преобразование целочисленных типов в типы указателей и наоборот или преобразование одного типа указателя в другой. Общую идею я вам это непереносимых и его следует избегать.
где я немного смущен, это одно использование, которое мне нужно, я вызываю C++ из C, и код C должен держаться за объект C++, поэтому в основном он содержит void*
. Какой бросок следует использовать для преобразования между void *
а тип класса?
Я видел использование как static_cast
и reinterpret_cast
? Хотя из того, что я читал, это кажется static
лучше, поскольку приведение может произойти во время компиляции? Хотя он говорит использовать reinterpret_cast
to преобразования из одного типа указателя к другому?
10 ответов
стандарт C++ гарантирует следующее:
static_cast
ing указатель на и из 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
чтобы вернуть исходный указатель.