4.9.9d Operador reinterpret_cast
§1 Sinopsis
Las conversiones de tipo que se realizan con este operador podríamos decir que son por la "fuerza bruta". Obligamos al compilador a aceptar un tipo de objeto por otro sin rechistar, por muy ilógica que sea la transformación (de ahí el nombre).
Ni que decir tiene que tales transformaciones son de lo más peligroso y que deben ser realizadas con extrema precaución. En realidad se suelen utilizar solo de forma temporal, para realizar determinadas transformaciones en los objetos [1] y volver a interpretarlos en su sentido original. Aunque en ocasiones esté perfectamente justificado, su utilización puede ser síntoma de una técnica deficiente o de que el programador se está metiendo en problemas.
§2 Sintaxis
reinterpret_cast<T> (arg)
§3 Descripción
El operando <T> (denominado tipo), determina el resultado de la conversión. Puede ser cualquiera de los
tipos siguientes: puntero ( 4.2);
referencia (
Es recomendable abandonar la antigua sintaxis (tipoX)expr, usando en su lugar el nuevo operador reinterpret_cast<tipoX>(expr) en todas aquellas conversiones que no sean seguras, o que sean dependientes de la implementación.
§4 Ejemplo
#include <iostream.h>
int main() { // =============
int x = 10;
float f = 20.5;
int* ptx = &x; // puntero-a-entero
float* ptf = &f; // puntero-a-float
cout << "ptr-a-X = " << ptx << endl;
cout << "ptr-a-F = " << ptf << endl;
cout << "Valor X = " << *ptx << endl;
cout << "Valor F = " << *ptf << endl;
ptx = ptf;
// L.14: Error !!
ptx = reinterpret_cast<int*> (ptf); // L.15: Ok.
cout << "ptr-a-F = " << ptx << endl;
cout << "Valor F = " << *ptx << endl;
}
Salida (después de eliminada la sentencia errónea):
ptr-a-X = 0065FE00
ptr-a-F = 0065FDFC
Valor X = 10
Valor F = 20.5
ptr-a-F = 0065FDFC
Valor F = 1101266944
Comentario
Las cuatro primeras salidas del ejemplo son una simple comprobación. Obtenemos las posiciones de memoria (hexadecimales) señaladas por ambos punteros, así como los valores (decimales) de las variables apuntadas.
En L.14 comprobamos como no es posible asignar un puntero-a-float a un puntero-a-int. El compilador avisa que no es posible realizar el "casting" implícito mediante un mensaje de error:
Cannot convert 'float *' to 'int *' in function main().
En L.15 realizamos la asignación sin novedad después de forzar un modelado con el operador reinterpret_cast adecuado para estos casos. A partir de este momento, ptx adopta el valor señalado por ptf.
Las dos últimas salidas nos muestran que la dirección sigue siendo correcta. ptx señala la misma dirección que ptf, pero el valor obtenido para f es erróneo, evidenciando así los peligros potenciales de este tipo de modelado.
La razón del error es obvia: el compilador interpreta el patrón de bits alojado a partir de la dirección 0065FDFC como un tipo entero, cuando en realidad corresponde a un float, cuyo modelo de almacenamiento es totalmente distinto ( 2.2.4a). Bajo la óptica de un entero es el valor 1101266944, mientras que interpretado como float es 20.5
Ejemplo relativo a la conversión de tipo de un puntero a clase ( Ejemplo).
§5 Un puntero puede ser convertido explícitamente a un tipo numérico entero. Lo cual es
lógico, dado que los punteros almacenan direcciones de memoria, que son magnitudes escalares enteras.
Recíprocamente, un argumento arg entero puede ser convertido a un puntero. La conversión de un puntero en un entero y posterior conversión a puntero del tipo original, conduce al valor original.
Ejemplo:
#include <iostream.h>
int main() { // ==================
int x, y;
float f = 20.5;
float* ptf = &f; // puntero-a-float
cout << "ptr-a-F = " << ptf << endl;
cout << "Valor F = " << *ptf << endl;
x = reinterpret_cast<int> (ptf); // convertimos a entero
ptf = reinterpret_cast<float*> (x); // reconversión a tipo original
cout << "ptr-a-F = " << ptf << endl;
cout << "Valor F = " << *ptf << endl;
}
Salida:
ptr-a-F = 0065FE00
Valor F = 20.5
ptr-a-F = 0065FE00
Valor F = 20.5
§6 En la conversión de un puntero o una referencia puede utilizarse una clase que aún no haya sido
definida. Ejemplo:
#include <iostream.h>
class C; // L.2: declaración adelantada
C* cptr;
class D {public: unsigned short us;}; // L.4:
D* dptr;
int main() { // ============
D d = D(43); // L.8
dptr = &d; // L.9
cout << "d.us = " << dptr->us << endl;
cptr = reinterpret_cast<C*>(dptr); // L.12:
class ::C { public: char ch; }; // L.14:
C c = { 'a' };
// L.15:
cout << "c.ch = " << c.ch << endl;
cout << "c.ch = " << cptr->ch << endl;
}
Salida:
d.us = 43
c.ch = a
c.ch = +
Comentario
El ejemplo tiene solo interés académico, pero muestra un par de características interesantes:
En L.2 y L.3 declaramos una clase C y un puntero cptr a ella sin estar definida todavía. Se trata de una declaración adelantada ( 4.11.4).
En líneas 4 y 5 definimos una nueva clase D y declaramos un puntero dptr.
En L.8 se instancia un objeto d de la clase D y es iniciado a un valor concreto.
En L.9 iniciamos el puntero dptr al nuevo objeto. En L.10 comprobamos el valor de la única propiedad us del objeto. Es la primera salida, que nos muestra el valor (43) esperado.
En L.12 asignamos dptr a cptr mediante un modelado adecuado. Observe que este modelado a tipo C*, se realiza sin que la clase haya sido definida todavía, cosa que hacemos en L.14. Observe que en esta sentencia de definición de la clase C, utilizamos el operador de acceso a ámbito :: ( 4.9.19). Sin él, estaríamos definiendo una nueva clase C, local a main. en vez de la declarada en el ámbito global.
En L.15 instanciamos un objeto c de la clase y lo iniciamos a un valor concreto. Las dos últimas líneas son de comprobación del valor de la única propiedad ch, del nuevo objeto. En la salida en que utilizamos el selector directo de miembro . ( 4.9.16) obtenemos el valor (a) esperado, pero en la última, en la que utilizamos el selector indirecto ->, obtenemos un valor (+) erróneo. La razón es que estamos accediendo a la variable ch mediante el puntero dptr "disfrazado" de puntero-a-C. Justamente señala un valor 43, que si es considerado como carácter, es el '+' que obtenemos en la última salida.
§7 Un puntero a función (
4.2.4) puede ser convertido explícitamente a puntero a objeto
( 4.2.1), suponiendo que el puntero a
objeto tenga suficientes bits para alojar el puntero a función. Recíprocamente, un puntero a objeto puede ser convertido en
puntero a función solamente en el caso de que este último sea suficientemente grande para alojar el puntero a objeto.
§8 En el ejemplo que sigue, mostramos el modelado de un puntero-a-función de
un tipo, a puntero-a-función de otro tipo, y como estas transformaciones conducen fácilmente a resultados erróneos.
#include <iostream.h>
void func(void* v) {
// L.2:
int x = reinterpret_cast<int>(v); // L.3:
cout << "X = " << x << endl;
}
void main() {
func(reinterpret_cast<void*>(5)); // L.7:
typedef void (* PFV)();
// L.8:
PFV pfunc = reinterpret_cast<PFV>(func); // L.9:
pfunc();
// L.10:
}
Salida:
X = 5
X = 6684208
Comentario
L.2: Define func como función recibiendo puntero genérico y devolviendo void.
L.3: El entero x recibe el valor del argumento pasado a la función después de un modelado adecuado.
L.7: Se invoca func pasando el valor 5 que previamente ha recibido un modelado adecuado al tipo de argumento que espera la función. La primera salida, proporcionada por la función es correcta. El entero 5 ha sido transformado a puntero-a-void y después vuelto a entero, con lo que recobra su valor.
L.8: Declara PFV puntero-a-función que no recibe argumentos devolviendo void.
L.9: Define pfunc como puntero-a-función que no recibe argumentos devolviendo void, y lo inicia a la función func. Como esta no corresponde con el tipo del puntero, se realiza un modelado previo a la asignación.
L.10: Se invoca func a través de su puntero. La salida es basura porque la invocación no proporciona los argumentos adecuados.
[1] En palabras de Bruce Eckel ( Thinking y C++), la clase de operaciones que se realizan con los tipos resultantes de estas transformaciones, son "rotaciones de bits o algún otro misterioso propósito". En mi modesta opinión, si programar C++ sin un conocimiento profundo de sus fundamentos puede ser tan peligroso como "meterse en la jaula de los leones", utilizar sus técnicas de modelado, especialmente de punteros a clases, puede ser tan peligroso como "meterse borracho en la jaula de los leones"