Estou pouco confuso com a aplicabilidade do reinterpret_cast
vs static_cast
. Pelo que li, as regras gerais são usar conversão estática quando os tipos podem ser interpretados em tempo de compilação, daí a palavra static
. Esse é o elenco que o compilador C ++ usa internamente também para lançamentos implícitos.
reinterpret_cast
s são aplicáveis em dois cenários:
- converter tipos inteiros em tipos de ponteiros e vice-versa
- converter um tipo de ponteiro para outro. A idéia geral que recebo é que isso não é portável e deve ser evitado.
Onde estou um pouco confuso é um uso que eu preciso, estou chamando C ++ de C e o código C precisa se apegar ao objeto C ++, então basicamente ele contém um void*
. Que elenco deve ser usado para converter entre o void *
tipo e o tipo de classe?
Eu já vi o uso de ambos static_cast
e reinterpret_cast
? Embora pelo que tenho lido, parece que static
é melhor, pois o elenco pode acontecer em tempo de compilação? Embora diga para usar reinterpret_cast
para converter de um tipo de ponteiro para outro?
reinterpret_cast
não acontece no tempo de execução. Ambos são instruções em tempo de compilação. De en.cppreference.com/w/cpp/language/reinterpret_cast : "Diferentemente de static_cast, mas como const_cast, a expressão reinterpret_cast não compila com nenhuma instrução da CPU. É puramente uma diretiva de compilador que instrui o compilador a tratar a sequência de bits (representação de objeto) da expressão como se tivesse o tipo new_type. "Respostas:
O padrão C ++ garante o seguinte:
static_cast
um ponteiro de e paravoid*
preserva o endereço. Ou seja, a seguira
,,b
ec
todos apontam para o mesmo endereço:reinterpret_cast
garante apenas que, se você converter um ponteiro para um tipo diferente e depoisreinterpret_cast
retornar ao tipo original , obterá o valor original. Então, no seguinte:a
ec
contém o mesmo valor, mas o valor deb
não é especificado. (na prática, normalmente conterá o mesmo endereço quea
ec
, mas isso não está especificado no padrão e pode não ser verdade em máquinas com sistemas de memória mais complexos.)Para fundição de e para
void*
,static_cast
deve ser preferido.fonte
b
não é mais não especificado no C ++ 11 ao usarreinterpret_cast
. E em C ++ 03 um elenco deint*
quevoid*
foi proibido de ser feito comreinterpret_cast
(embora compiladores não implementar isso e que era impraticável, portanto, foi mudado para C ++ 11).Um caso em que
reinterpret_cast
é necessário é a interface com tipos de dados opacos. Isso ocorre frequentemente nas APIs do fornecedor sobre as quais o programador não tem controle. Aqui está um exemplo artificial em que um fornecedor fornece uma API para armazenar e recuperar dados globais arbitrários:Para usar essa API, o programador deve converter seus dados para
VendorGlobalUserData
e novamente.static_cast
não vai funcionar, é preciso usarreinterpret_cast
:Abaixo está uma implementação artificial da API de exemplo:
fonte
void*
para isso?USpoofChecker*
, ondeUSpoofChecker
é uma estrutura vazia. No entanto, sob o capô, sempre que você passa umUSpoofChecker*
, ele sofrereinterpret_cast
um tipo interno de C ++.A resposta curta: se você não sabe o que
reinterpret_cast
significa, não use. Se você precisar no futuro, saberá.Resposta completa:
Vamos considerar os tipos básicos de números.
Quando você converte, por exemplo,
int(12)
emunsigned float (12.0f)
seu processador, precisa invocar alguns cálculos, pois os dois números têm uma representação de bits diferente. Isto é o questatic_cast
significa.Por outro lado, quando você chama
reinterpret_cast
a CPU, não invoca nenhum cálculo. Apenas trata um conjunto de bits na memória como se tivesse outro tipo. Portanto, quando você converteint*
parafloat*
com essa palavra-chave, o novo valor (após a redução da referência do ponteiro) não tem nada a ver com o valor antigo em significado matemático.Exemplo: É verdade que
reinterpret_cast
não é portátil devido a uma ordem de razão de bytes (endianness). Mas isso é surpreendentemente o melhor motivo para usá-lo. Vamos imaginar o exemplo: você precisa ler o número binário de 32 bits do arquivo e sabe que é big endian. Seu código precisa ser genérico e funciona corretamente em sistemas big endian (por exemplo, alguns ARM) e little endian (por exemplo, x86). Então você tem que verificar a ordem dos bytes.É bem conhecido no tempo de compilação para que você possa escrever aVocê pode escrever uma função para conseguir isso:constexpr
função:Explicação: a representação binária de
x
na memória pode ser0000'0000'0000'0001
(grande) ou0000'0001'0000'0000
(pouco endian). Após reinterpretar a conversão, o byte sob op
ponteiro pode ser respectivamente0000'0000
ou0000'0001
. Se você usar projeção estática, sempre será0000'0001
, independentemente de qual endianness esteja sendo usado.EDITAR:
Na primeira versão que fiz exemplo função
is_little_endian
de serconstexpr
. Ele compila bem no mais recente gcc (8.3.0), mas o padrão diz que é ilegal. O compilador clang se recusa a compilá-lo (o que está correto).fonte
short
leva 16 bits na memória. Corrigido.O significado de
reinterpret_cast
não é definido pelo padrão C ++. Portanto, em teoria, umreinterpret_cast
poderia travar seu programa. Na prática, os compiladores tentam fazer o que você espera, que é interpretar os bits do que você está passando como se fossem do tipo para o qual você está transmitindo. Se você sabe o que os compiladores que você vai usar fazem,reinterpret_cast
pode usá-lo, mas dizer que é portátil estaria mentindo.Para o caso que você descreve, e praticamente qualquer caso em que possa considerar
reinterpret_cast
, você pode usarstatic_cast
ou alguma outra alternativa. Entre outras coisas, o padrão tem a dizer sobre o que você pode esperarstatic_cast
(§5.2.9):Portanto, para o seu caso de uso, parece bastante claro que o comitê de padronização pretendia que você usasse
static_cast
.fonte
reinterpret_crash
. De maneira alguma um bug do compilador me impedirá de travar meu programa de reinterpretação. Vou relatar um erro o mais rápido possível </ ironia>!template<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
Um uso do reinterpret_cast é se você deseja aplicar operações bit a bit para flutuadores (IEEE 754). Um exemplo disso foi o truque Fast Inverse Square-Root:
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
Ele trata a representação binária do flutuador como um número inteiro, o desloca para a direita e o subtrai de uma constante, diminuindo pela metade e negando o expoente. Depois de converter novamente em um flutuador, ele é submetido a uma iteração de Newton-Raphson para tornar essa aproximação mais exata:
Isso foi originalmente escrito em C, então usa conversão de C, mas a conversão de C ++ análoga é a reinterpret_cast.
fonte
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))
# ideone.com/6S4ijcreinterpret_cast
pormemcpy
, ainda é UB?memcpy
definitivamente tornaria legal.Você pode usar reinterprete_cast para verificar a herança em tempo de compilação.
Veja aqui: Usando reinterpret_cast para verificar a herança em tempo de compilação
fonte
Tentei concluir e escrevi um elenco seguro simples usando modelos. Observe que esta solução não garante converter ponteiros em uma função.
fonte
reinterpret_cast
já faz nesta situação: "Um ponteiro de objeto pode ser explicitamente convertido em um ponteiro de objeto de um tipo diferente. [72] Quando um valor inicialv
do tipo de ponteiro de objeto é convertido no tipo de ponteiro de objeto" ponteiro para cvT
", o resultado éstatic_cast<cv T*>(static_cast<cv void*>(v))
". - N3797.c++2003
padrão I pode não achar quereinterpret_cast
fazstatic_cast<cv T*>(static_cast<cv void*>(v))
C++03
eraC++98
. Toneladas de projetos usavam C ++ antigo em vez de C. portátil. Às vezes, você precisa se preocupar com a portabilidade. Por exemplo, você precisa suportar o mesmo código no Solaris, AIX, HPUX, Windows. No que diz respeito à dependência e portabilidade do compilador, é complicado. Assim, um bom exemplo da introdução de um inferno portabilidade é usar umreinterpret_cast
no seu códigoPrimeiro você tem alguns dados em um tipo específico como int aqui:
Então você deseja acessar a mesma variável que outro tipo, como float: Você pode decidir entre
ou
BREVE: significa que a mesma memória é usada como um tipo diferente. Assim, você pode converter representações binárias de carros alegóricos como tipo int, como acima, em carros alegóricos. 0x80000000 é -0, por exemplo (a mantissa e o expoente são nulos, mas o sinal, o msb, é um. Isso também funciona para duplos e duplos longos.
OTIMIZE: Acho que reinterpret_cast seria otimizado em muitos compiladores, enquanto a conversão c é feita por pointeraritmética (o valor deve ser copiado para a memória, porque os ponteiros não podem apontar para os registradores de CPU).
NOTA: Nos dois casos, você deve salvar o valor convertido em uma variável antes da conversão! Essa macro pode ajudar:
fonte
reinterpret_cast
formaint
afloat&
é um comportamento indefinido.Uma razão para usar
reinterpret_cast
é quando uma classe base não possui uma tabela v, mas uma classe derivada possui. Nesse caso,static_cast
ereinterpret_cast
resultará em diferentes valores de ponteiro (este seria o caso atípico mencionado por jalf acima ). Apenas como um aviso, não estou afirmando que isso faz parte do padrão, mas a implementação de vários compiladores generalizados.Como exemplo, pegue o código abaixo:
Que gera algo como:
Em todos os compiladores que tentei (MSVC 2015 e 2017, clang 8.0.0, gcc 9.2, icc 19.0.1 - veja godbolt nos últimos 3 ), o resultado da diferença
static_cast
é dereinterpret_cast
2 (4 para MSVC). O único compilador a alertar sobre a diferença foi clang, com:Uma última ressalva é que, se a classe base não tiver membros de dados (por exemplo, the
int i;
), clang, gcc e icc retornam o mesmo endereço para oreinterpret_cast
que parastatic_cast
, enquanto o MSVC ainda não.fonte
Aqui está uma variante do programa de Avi Ginsburg que ilustra claramente a propriedade
reinterpret_cast
mencionada por Chris Luengo, flodin e cmdLP: que o compilador trata o local da memória apontada como se fosse um objeto do novo tipo:O que resulta em resultados como este:
Pode-se observar que o objeto B é construído na memória como dados específicos de B primeiro, seguido pelo objeto A incorporado. O
static_cast
retorna corretamente o endereço do objeto Um incorporado, eo ponteiro criado porstatic_cast
corretamente fornece o valor do campo de dados. O ponteiro gerado porreinterpret_cast
guloseimasb
localização da memória de como se fosse um objeto A simples; portanto, quando o ponteiro tenta obter o campo de dados, ele retorna alguns dados específicos de B como se fossem o conteúdo desse campo.Um uso de
reinterpret_cast
é converter um ponteiro em um número inteiro não assinado (quando ponteiros e números inteiros não assinados têm o mesmo tamanho):int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
fonte
Resposta rápida: use
static_cast
se compilar, caso contrário, recorra areinterpret_cast
.fonte
Leia o FAQ ! Manter dados C ++ em C pode ser arriscado.
No C ++, um ponteiro para um objeto pode ser convertido
void *
sem nenhuma conversão . Mas não é verdade o contrário. Você precisaria de umstatic_cast
para recuperar o ponteiro original.fonte