Pregunta

Estoy leyendo un libro y me encontré con que reinterpret_cast no debe utilizarse directamente, sino más bien fundición a void * en combinación con static_cast:

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

En lugar de:

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

Sin embargo, no puedo encontrar una explicación de por qué es mejor que el reparto directo. Yo muy agradecido si alguien me puede dar una explicación o que me señale la respuesta.

Gracias de antemano

p.s. Yo sé lo que se utiliza para reinterpret_cast, pero nunca he visto que se usa de esta manera

¿Fue útil?

Solución

Para los tipos para las que se permita tal fundido (por ejemplo, si T1 es una de tipo POD y T2 es unsigned char), el enfoque con static_cast está bien definido por el estándar.

Por otro lado, reinterpret_cast es totalmente definido por la implementación - la única garantía de que se obtiene por ello es que se puede emitir un tipo de puntero a cualquier otro tipo de puntero y luego de vuelta, y obtendrá el valor original; y también, puede convertir un tipo de puntero a un tipo entero suficientemente grande para contener un valor de puntero (que varía dependiendo de la aplicación, y no necesita existir en absoluto), y luego se echó hacia atrás, y obtendrá el valor original.

Para ser más específicos, sólo voy a citar las partes pertinentes de la Norma, destacando partes importantes:

5.2.10 [expr.reinterpret.cast]:

  

El mapeo realizado por reinterpret_cast es definido por la implementación . [Nota:. Podría, o no puede, producir una representación diferente del valor original] ... Un puntero a un objeto se puede convertir explícitamente a un puntero a un objeto de diferente tipo), excepto que la conversión de un valor p de tipo. “puntero a T1” a la “puntero a T2” tipo (donde T1 y T2 son tipos de objetos y donde los requisitos de alineación de T2 no son más estrictos que los de T1) y de vuelta a su tipo original produce el valor del puntero original, el resultado de dicha conversión puntero no se especifica .

Así que algo como esto:

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

es efectivamente no especificado.

Al explicar por qué funciona la static_cast es un poco más complejo. Aquí está el código anterior reescrito para usar static_cast que creo que se garantiza que funcione siempre según lo previsto en las normas:

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

Una vez más, permítanme citar las secciones de la Norma que, en conjunto, me llevan a la conclusión de que lo anterior debe ser portátil:

3.9 [basic.types]:

  

Para cualquier objeto (distinto de un subobjeto de la clase base) de tipo POD T, si o no el objeto contiene un valor válido de tipo T, los bytes subyacentes (1,7) que forman el objeto se puede copiar en una matriz de CHAR o unsigned char. Si el contenido de la matriz de char o unsigned char se copia de nuevo en el objeto, el objeto deberá mantener posteriormente su valor original.

     

La representación de objeto de un objeto de tipo T es la secuencia de N unsigned char objetos ocupado por el objeto de tipo T, donde N es igual sizeof (T).

3.9.2 [basic.compound]:

  

Objetos de cv-cualificado (3.9.3) o de tipo void* cv-calificada (puntero a void), se pueden utilizar para apuntar a objetos de tipo desconocido. Un void* será capaz de sostener cualquier objeto puntero. A (3.9.3) void* cv-calificado o no calificado cv-tendrá los mismos requisitos de representación y de alineación como una char* cv-calificado o no calificado cv-.

3,10 [basic.lval]:

  

Si un programa intenta acceder al valor almacenado de un objeto a través de un lvalue distinta de uno de los siguientes tipos el comportamiento es indefinido):

     
      
  • ...
  •   
  • un char o unsigned tipo char .
  •   

4,10 [conv.ptr]:

  

Un valor p de tipo “puntero a cv T”, donde T es un tipo de objeto, se puede convertir en un valor p de tipo “puntero a void cv.” El resultado de la conversión de un “puntero a cv T” a un “ puntero a void cv”apunta al comienzo de la ubicación de almacenamiento donde el objeto de tipo T reside, como si el objeto es un objeto más derivada (1.8) de tipo T (es decir, no un subobjeto clase base).

5.2.9 [expr.static.cast]:

  

La inversa de cualquier secuencia de conversión estándar (cláusula 4), aparte de la-lvalue-a rvalue (4.1), matriz de topointer (4.2), de función-a-puntero (4.3) y (4.12) boolean conversiones, se puede realizar de manera explícita usando static_cast.

[EDIT] Por otro lado, tenemos esta joya:

9,2 [class.mem] / 17:

  

Un puntero a un objeto de POD-struct, adecuadamente convertido usando un reinterpret_cast, apunta a su miembro inicial (o si ese miembro es un campo de bits, a continuación, a la unidad en la que reside) y viceversa. [Nota: Es posible que por lo tanto, es el relleno no identificado dentro de un objeto POD-estructura, pero no en su comienzo, si es necesario para lograr la alineación adecuada. ]

que parece dar a entender que reinterpret_cast entre los punteros implica de alguna manera "misma dirección". Imagínate.

Otros consejos

No hay la más mínima duda de que la intención es que ambas formas están bien definidos, pero la redacción no logra captar eso.

Las dos formas funcionarán en la práctica.

reinterpret_cast es más explícito acerca de la intención y debe preferirse.

La verdadera razón por la que esto es así es por la forma en C ++ define la herencia, y debido a los punteros miembros.

Con C, el puntero es prácticamente sólo una dirección, como debe ser. En C ++ que tiene que ser más compleja debido a algunas de sus características.

punteros miembros son realmente un desplazamiento dentro de una clase, por lo echaban ellos es siempre un desastre usando el estilo C.

Si usted ha heredado multiplican dos objetos virtuales que también tienen algunas piezas de hormigón, que también es un desastre para el estilo C. Este es el caso de la herencia múltiple que causa todos los problemas, sin embargo, por lo que no debe nunca querer usar esto de todos modos.

Realmente espero que nunca usa estos casos en el primer lugar. Además, si usted está lanzando una gran cantidad que es otra señal de que está metiendo en en su diseño.

La única vez que termino de fundición es con las primitivas en las zonas C ++ decide no son los mismos, pero, obviamente, donde tienen que estar. Para los objetos reales, cada vez que quieres lanzar algo, empiezan a cuestionar su diseño, ya que debe ser 'la programación de la interfaz de' la mayor parte del tiempo. Por supuesto, no se puede cambiar la forma en tercera API equipo de trabajo por lo que no siempre se tiene mucha opción.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top