lanzando a través de void * en lugar de usar reinterpret_cast

Estoy leyendo un libro y descubrí que reinterpret_cast no se debe usar directamente, sino que se debe convertir a void * en combinación con static_cast :

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

En lugar de:

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

Sin embargo, no puedo encontrar una explicación de por qué es esto mejor que el reparto directo. Agradecería mucho que alguien me diera una explicación o me indicara la respuesta.

Gracias por adelantado

ps sé para qué se usa reinterpret_cast , pero nunca vi que se use de esta manera

Para los tipos para los que se permite tal conversión (por ejemplo, si T1 es un tipo POD y T2 es unsigned char ), el enfoque con static_cast está bien definido por el Estándar.

Por otro lado, reinterpret_cast está completamente definido por la implementación; la única garantía que obtienes es que puedes convertir un tipo de puntero en cualquier otro tipo de puntero y luego volver, y obtendrás el valor original; y también, puede convertir un tipo de puntero a un tipo integral lo suficientemente grande como para contener un valor de puntero (que varía según la implementación, y no necesita existir en absoluto), y luego devolverlo, y obtendrá el valor original.

Para ser más específico, citaré solo las partes relevantes del Estándar, resaltando las partes importantes:

5.2.10 [expr.reinterpret.cast]:

La asignación realizada por reinterpret_cast está definida por la implementación . [Nota: podría, o no, producir una representación diferente del valor original.] … Un puntero a un objeto se puede convertir explícitamente en un puntero a un objeto de tipo diferente.) Excepto que la conversión de un valor r de tipo “Puntero a T1” al tipo “puntero a T2” (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 tal conversión de puntero no se especifica .

Entonces algo como esto:

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

es efectivamente no especificado.

Explicar por qué static_cast funciona es un poco más complicado. Aquí está el código anterior reescrito para usar static_cast que creo que está garantizado para funcionar siempre según lo previsto por el estándar:

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

Nuevamente, permítanme citar las secciones del Estándar que, juntas, me llevan a concluir que lo anterior debe ser portátil:

3.9 [basic.types]:

Para cualquier objeto (que no sea un subobjeto de clase base) de POD tipo T, tanto si el objeto tiene un valor válido de tipo T como si no, los bytes subyacentes (1.7) que componen el objeto se pueden copiar en una matriz de caracteres o sin signo carbonizarse. Si el contenido de la matriz de char o char sin signo se copia de nuevo en el objeto, el objeto conservará posteriormente su valor original.

La representación del objeto de un objeto de tipo T es la secuencia de N objetos de signo sin signo tomados por el objeto de tipo T, donde N es igual a sizeof (T).

3.9.2 [basic.compound]:

Los objetos de tipo void* (3.9.3) o cv-no calificado (puntero a vacío) se pueden usar para señalar objetos de tipo desconocido. Un void* deberá ser capaz de contener cualquier puntero de objeto. Un void* calificado por cv o cv no calificado (3.9.3) void* tendrá los mismos requisitos de representación y alineamiento que un carácter calificado cv o no calificado por cv .

3.10 [basic.lval]:

Si un progtwig intenta acceder al valor almacenado de un objeto a través de un lvalor distinto de uno de los siguientes tipos, el comportamiento no está definido):

  • un char o un tipo de carácter sin signo .

4.10 [conv.ptr]:

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

5.2.9 [expr.static.cast]:

La inversa de cualquier secuencia de conversión estándar (cláusula 4), distinta de las conversiones lvalue-to-rvalue (4.1), array-topointer (4.2), función-a-puntero (4.3) y booleana (4.12), se puede realizar usando explícitamente static_cast.

[EDITAR] Por otro lado, tenemos esta joya:

9.2 [class.mem] / 17:

Un puntero a un objeto POD-struct, adecuadamente convertido utilizando un reinterpret_cast, apunta a su miembro inicial (o si ese miembro es un campo de bits, luego a la unidad en la que reside) y viceversa. [Nota: por lo tanto, podría haber un relleno sin nombre dentro de un objeto POD-struct, pero no al principio, según sea necesario para lograr la alineación adecuada. ]

lo que parece implicar que reinterpret_cast entre punteros de alguna manera implica “misma dirección”. Imagínate.

No hay la menor duda de que la intención es que ambas formas estén bien definidas, pero la redacción no logra capturar eso.

Ambas formas funcionarán en la práctica.

reinterpret_cast es más explícito sobre la intención y debe ser preferido.

La verdadera razón por la que esto es así es por la forma en que C ++ define la herencia y por los indicadores de los miembros.

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

Los punteros de los miembros son realmente una compensación en una clase, por lo que lanzarlos siempre es un desastre usando el estilo C.

Si ha multiplicado dos objetos virtuales heredados que también tienen partes concretas, eso también es un desastre para el estilo C. Sin embargo, este es el caso de la herencia múltiple que causa todos los problemas, por lo que nunca deberías querer usar esto de todos modos.

Realmente espero que nunca uses estos casos en primer lugar. Además, si estás lanzando mucho, es otra señal de que estás cometiendo un error en tu diseño.

La única vez que termino fundiendo es con los primitivos en áreas que C ++ decide que no son lo mismo pero donde obviamente tienen que estar. Para los objetos reales, cada vez que desee lanzar algo, comience a cuestionar su diseño porque debería estar ‘progtwigndo en la interfaz’ la mayor parte del tiempo. Por supuesto, no puede cambiar cómo funcionan las API de terceros, por lo que no siempre tiene muchas opciones.