Pergunta

Eu estou lendo um livro e descobri que reinterpret_cast não deve ser usado diretamente, mas sim lançando para void * em combinação com static_cast:

T1 * p1=...
void *pv=p1;
T2 * p2= static_cast<T2*>(pv);

Em vez de:

T1 * p1=...
T2 * p2= reinterpret_cast<T2*>(p1);

No entanto, não consigo encontrar uma explicação por que isso é melhor do que o elenco direta. Eu muito apreciaria se alguém pode me dar uma explicação ou me aponte para a resposta.

Agradecemos antecipadamente

P.S. Eu sei o que é reinterpret_cast utilizado para, mas eu nunca vi que é usado dessa maneira

Foi útil?

Solução

Para os tipos para os quais é permitida tal fundido (por exemplo, se T1 é um tipo POD e T2 é unsigned char), a abordagem com static_cast é bem definida pelo padrão.

Por outro lado, reinterpret_cast é inteiramente definido pela implementação - a única garantia que você tem para isso é que você pode lançar um tipo de ponteiro para qualquer outro tipo de ponteiro e depois voltar, e você vai obter o valor original; E também, você pode lançar um tipo de ponteiro para um grande o suficiente tipo integral para manter um valor de ponteiro (que varia de acordo com a implementação e necessidades não existem), e depois lançá-lo de volta, e você obterá o valor original.

Para ser mais específico, eu vou apenas citar as partes relevantes do Padrão, destacando partes importantes:

5.2.10 [expr.reinterpret.cast]:

O mapeamento executada por reinterpret_cast é definido implementação-. [Nota:. Que pode, ou não pode, produzir uma representação diferente do valor original] ... Um apontador para um objecto pode ser explicitamente convertido para um apontador para um objecto de tipo diferente), excepto que a conversão de um rvalue de tipo. “ponteiro para T1” para o “ponteiro para T2” tipo (em que T1 e T2 são tipos de objectos e onde os requisitos de alinhamento de T2 são mais rigorosas do que as do T1) e de volta ao seu tipo original produz o valor de ponteiro original, o resultado de uma tal conversão ponteiro é indeterminado .

Então, algo como isto:

struct pod_t { int x; };
pod_t pod;
char* p = reinterpret_cast<char*>(&pod);
memset(p, 0, sizeof pod);

é efetivamente não especificado.

Explicando porque static_cast funciona é um pouco mais complicado. Aqui está o código acima reescrito para uso static_cast que eu acredito que é garantido para sempre trabalho como pretendido pelo Standard:

struct pod_t { int x; };
pod_t pod;
char* p = static_cast<char*>(static_cast<void*>(&pod));
memset(p, 0, sizeof pod);

Mais uma vez, deixe-me citar as seções do padrão que, em conjunto, levam-me a concluir que o acima deve ser portátil:

3.9 [basic.types]:

Para qualquer objecto (que não seja um subobjecto base de classe) de tipo POD T, se ou não o objecto mantém um valor válido de tipo T, os bytes subjacentes (1,7) que constituem o objecto pode ser copiada para uma matriz de carvão ou carvão animal sem sinal. Se o conteúdo da matriz de char ou unsigned char é copiado de volta para o objeto, o objeto será posteriormente realizar o seu valor original.

A representação objecto de um objecto do tipo T é a sequência de N unsigned char objetos tomado pelo objecto de tipo T, onde N é igual a sizeof (t).

3.9.2 [basic.compound]:

Objectos de cv-qualificado (3.9.3) ou CV-não qualificado tipo void* (ponteiro para vazio), pode ser utilizado para apontar para objectos de tipo desconhecido. A void* deve ser capaz de segurar qualquer objeto ponteiro. A (3.9.3) void* cv-qualificado ou CV-não qualificado deve ter as mesmas exigências de representação e de alinhamento como uma char* cv-qualificado ou CV-não qualificado .

3.10 [basic.lval]:

Se um programa tenta acessar o valor armazenado de um objeto através de um lvalue diferente de um dos seguintes tipos do comportamento é indefinido):

  • ...
  • um char ou unsigned char tipo .

4.10 [conv.ptr]:

Um rvalue do tipo “ponteiro para cv t”, em que T é um tipo de objecto, pode ser convertido para um rvalue do tipo “ponteiro para vazio cv.” O resultado da conversão de um “ponteiro para cv T” para um “ponteiro para vazio cv” aponta para o início do local de armazenamento, onde o objecto de reside tipo T, como se o objecto for um objeto mais derivado (1.8) do tipo T (isto é, não um sub-objeto classe base).

5.2.9 [expr.static.cast]:

O inverso da sequência de qualquer conversão padrão (cláusula 4), que não seja o rvalue Ivalue-a-(4.1), matriz-topointer (4.2), de função-para-ponteiro (4.3), e booleano (4.12) as conversões, pode ser realizada usando explicitamente static_cast.

[EDIT] Por outro lado, temos esta pérola:

9.2 [class.mem] / 17:

Um apontador para um objecto POD-estrutura, adequadamente convertido utilizando um reinterpret_cast, aponta para o seu membro inicial (ou se este membro é um campo de bits, em seguida, para a unidade na qual reside) e vice-versa. [Nota: Não pode , portanto, ser estofamento sem nome dentro de um objeto POD-estrutura, mas não em seu início, como necessário para alcançar o alinhamento apropriado. ]

que parece implicar que reinterpret_cast entre ponteiros de alguma forma implica "mesmo endereço". Vai saber.

Outras dicas

Não há a menor dúvida de que a intenção é que ambas as formas são bem definidos, mas o texto não consegue captar isso.

As duas formas irá funcionar na prática.

reinterpret_cast é mais explícito sobre a intenção e deve ser preferida.

A verdadeira razão para tal é por causa de como C ++ herança define, e por causa de ponteiros de membro.

Com C, ponteiro é praticamente apenas um endereço, como deveria ser. Em C ++, tem de ser mais complexo por causa de algumas de suas características.

ponteiros Membros são realmente um deslocamento em uma classe, para expulsá-los é sempre um desastre usando o estilo C.

Se você multiplicar herdou dois objetos virtuais que também têm algumas peças de concreto, que é também um desastre para o estilo C. Este é o caso de herança múltipla que faz com que todos os problemas, porém, assim que você não deve nunca querer usar isso de qualquer maneira.

Realmente espero que você nunca usa esses casos, em primeiro lugar. Além disso, se você está lançando um monte de que é outro sinal de que você está bagunçando no em seu projeto.

A única vez que eu acabar elenco está com os primitivos em áreas C ++ decide não são os mesmos, mas onde, obviamente, eles têm que ser. Para objetos reais, a qualquer hora que você quer elenco alguma coisa, começa a questionar seu projeto porque você deve ser 'a programação para a interface' a maior parte do tempo. Claro, você não pode mudar a forma como 3ª APIs partido trabalho para que você nem sempre têm muita escolha.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top