Los moldes se deben utilizar con cuidado, porque lo que está haciendo en realidad es decir al compilador «Olvida la comprobación de tipo - trátalo como si fuese de este otro tipo.» Esto significa, que está introduciendo un agujero en el sistema de tipos de C++ y evitando que el compilador informe de que está haciendo algo erróneo con un tipo. Lo que es peor, el compilador lo cree implícitamente y no realiza ninguna otra comprobación para buscar errores. Una vez ha comenzado a moldear, está expuesto a todo tipo de problemas. De hecho, cualquier programa que utilice muchos moldes se debe revisar con detenimiento, no importa cuanto haya dado por sentado que simplemente «debe» hacerse de esta manera. En general, los moldes deben ser pocos y aislados para solucionar problemas específicos.
Una vez se ha entendido esto y se presente un programa con errores, la primera impresión puede que sea mirar los moldes como si fuesen los culpables. Pero, ¿cómo encontrar los moldes estilo C? Son simplemente nombres de tipos entre paréntesis, y si se empieza a buscar estas cosas descubrirá que a menudo es difícil distinguirlos del resto del código.
El C++ Estándar incluye una sintaxis explícita de molde que se puede utilizar para reemplazar completamente los moldes del estilo antiguo de C (por supuesto, los moldes de estilo C no se pueden prohibir sin romper el código, pero los escritores de compiladores pueden advertir fácilmente acerca de los moldes antiguos). La sintaxis explícita de moldes está pensada para que sea fácil encontrarlos, tal como se puede observar por sus nombres:
static_cast
|
Para moldes que se comportan bien o razonablemente bien, incluyendo cosas que se podrían hacer sin un molde (como una conversión automática de tipo). |
const_cast
|
Para moldear const y/o volatile
|
reinterpret_cast
|
Para moldear a un significado completamente diferente. La clave es que se necesitará volver a moldear al tipo original para poderlo usar con seguridad. El tipo al que moldee se usa típicamente sólo para jugar un poco o algún otro propósito misterioso. Éste es el más peligroso de todos los moldes. |
dynamic_cast
|
Para realizar un downcasting seguro (este molde se describe en el Capítulo 15). |
Tabla 3.2. Moldes explícitos de C++
Los primeros tres moldes explícitos se describirán completamente en las siguientes secciones, mientras que los últimos se explicarán después de que haya aprendido más en el Capítulo 15.
El static_cast
se utiliza para todas las conversiones
que están bien definidas. Esto incluye conversiones
«seguras» que el compilador permitiría sin
utilizar un molde, y conversiones menos seguras que están sin
embargo bien definidas. Los tipos de conversiones que cubre
static_cast
incluyen las conversiones típicas sin
molde, conversiones de estrechamiento (pérdida de
información), forzar una conversión de un void*
,
conversiones de tipo implícitas, y navegación estática de
jerarquías de clases (ya que no se han visto aún clases ni
herencias, este último apartado se pospone hasta el Capítulo
15):
//: C03:static_cast.cpp void func(int) {} int main() { int i = 0x7fff; // Max pos value = 32767 long l; float f; // (1) Typical castless conversions: l = i; f = i; // Also works: l = static_cast<long>(i); f = static_cast<float>(i); // (2) Narrowing conversions: i = l; // May lose digits i = f; // May lose info // Says "I know," eliminates warnings: i = static_cast<int>(l); i = static_cast<int>(f); char c = static_cast<char>(i); // (3) Forcing a conversion from void* : void* vp = &i; // Old way produces a dangerous conversion: float* fp = (float*)vp; // The new way is equally dangerous: fp = static_cast<float*>(vp); // (4) Implicit type conversions, normally // performed by the compiler: double d = 0.0; int x = d; // Automatic type conversion x = static_cast<int>(d); // More explicit func(d); // Automatic type conversion func(static_cast<int>(d)); // More explicit } ///:~
Listado 3.39. C03/static_cast.cpp
En la sección (FIXME:xref:1), se pueden ver tipos de
conversiones que eran usuales en C, con o sin un
molde. Promover un int
a long
o
float
no es un problema porque el último puede
albergar siempre cualquier valor que un int
pueda contener. Aunque es innecesario, se puede utilizar
static_cast
para remarcar estas promociones.
Se muestra en (2) como se convierte al revés. Aquí, se puede
perder información porque un int
no es tan
«ancho» como un long
o un
float
; no aloja números del mismo tamaño. De
cualquier modo, este tipo de conversión se llama conversión de
estrechamiento. El compilador no impedirá que ocurran, pero
normalmente dará una advertencia. Se puede eliminar esta
advertencia e indicar que realmente se pretendía esto
utilizando un molde.
Tomar el valor de un void*
no está permitido en
C++ a menos que use un molde (al contrario de C), como se
puede ver en (3). Esto es peligroso y requiere que los
programadores sepan lo que están haciendo. El
static_cast
, al menos, es mas fácil de localizar que
los moldes antiguos cuando se trata de cazar fallos.
La sección (FIXME:xref:4) del programa muestra las
conversiones de tipo implícitas que normalmente se realizan
de manera automática por el compilador. Son automáticas y no
requieren molde, pero el utilizar static_cast
acentúa dicha acción en caso de que se quiera reflejar
claramente qué está ocurriendo, para poder localizarlo
después.
Si quiere convertir de un const
a un
no-const
o de un volatile
a un
no-volatile
, se utiliza const_cast
. Es la
única conversión permitida con const_cast
; si está
involucrada alguna conversión adicional se debe hacer
utilizando una expresión separada o se obtendrá un error en
tiempo de compilación.
//: C03:const_cast.cpp int main() { const int i = 0; int* j = (int*)&i; // Deprecated form j = const_cast<int*>(&i); // Preferred // Can't do simultaneous additional casting: //! long* l = const_cast<long*>(&i); // Error volatile int k = 0; int* u = const_cast<int*>(&k); } ///:~
Listado 3.40. C03/const_cast.cpp
Si toma la dirección de un objeto const
, produce un
puntero a const
, éste no se puede asignar a un
puntero que no sea const
sin un molde. El molde al
estilo antiguo lo puede hacer, pero el const_cast
es
el más apropiado en este caso. Lo mismo ocurre con
volatile
.
Este es el menos seguro de los mecanismos de molde, y el más
susceptible de crear fallos. Un reinterpret_cast
supone que un objeto es un patrón de bits que se puede
tratar (para algún oscuro propósito) como si fuese de un
tipo totalmente distinto. Ese es el jugueteo de bits a bajo
nivel por el cual C es famoso. Prácticamente siempre
necesitará hacer reinterpret_cast
para volver al
tipo original (o de lo contrario tratar a la variable como
su tipo original) antes de hacer nada más con ella.
//: C03:reinterpret_cast.cpp #include <iostream> using namespace std; const int sz = 100; struct X { int a[sz]; }; void print(X* x) { for(int i = 0; i < sz; i++) cout << x->a[i] << ' '; cout << endl << "--------------------" << endl; } int main() { X x; print(&x); int* xp = reinterpret_cast<int*>(&x); for(int* i = xp; i < xp + sz; i++) *i = 0; // Can't use xp as an X* at this point // unless you cast it back: print(reinterpret_cast<X*>(xp)); // In this example, you can also just use // the original identifier: print(&x); } ///:~
Listado 3.41. C03/reinterpret_cast.cpp
En este ejemplo, struct X
contiene un array de
int
, pero cuando se crea uno en la pila como en
X x
, los valores de cada uno de los
int
s tienen basura (esto se demuestra utilizando la
función print()
para mostrar los
contenidos de struct
). Para inicializarlas, la
dirección del X
se toma y se moldea a un puntero
int
, que es luego iterado a través del array para
inicializar cada int
a cero. Fíjese como el
límite superior de i
se calcula
«añadiendo» sz
a
xp
; el compilador sabe que lo que usted
quiere realmente son las direcciones de sz mayores que
xp
y él realiza el cálculo aritmético por
usted. FIXME(Comprobar lo que dice este párrafo de acuerdo con
el código)
La idea del uso de reinterpret_cast
es que cuando se
utiliza, lo que se obtiene es tan extraño que no se puede
utilizar para los propósitos del tipo original, a menos que se
vuelva a moldear. Aquí, vemos el molde otra vez a
X*
en la llamada a print()
,
pero por supuesto, dado que tiene el identificador original
también se puede utilizar. Pero xp
sólo es
útil como un int*
, lo que es
verdaderamente una «reinterpretación» del X
original.
Un reinterpret_cast
a menudo indica una programación
desaconsejada y/o no portable, pero está disponible si decide
que lo necesita.