Inicio > Programación > C/C++ : Tipos de cast

C/C++ : Tipos de cast

Sábado, 5 de diciembre de 2009 Dejar un comentario Ir a comentarios

cpp1A pesar de llevar bastante tiempo programando en C/C++ parece que uno no llega a conocer nunca a fondo todas las características que posee un lenguaje determinado. En esta entrada quiero hablaros de los distintos tipos de conversiones entre datos, ya que así me sirve también a mi de nota recordatoria cuando sea necesario :P . La mayor parte de información que  os muestro a continuación a sido recopilada de internet, pero me he asegurado de comprobar que toda ella es veraz realizando las comprobaciones oportunas en mi ordenador y por supuesto añadiendo notas personales. Allá vamos …

static_cast

static_cast es el primer tipo de conversión al que solemos recurrir. Realiza conversiones implícitas entre tipos (como un int a float, o un puntero de un tipo determinado a void*), y también puede realizar conversiones explícitas. En muchos casos, establecer explícitamente un static_cast no es necesario, aunque es importante tener en cuenta que la sintaxis de conversiones de C Tipo(variable) y/o (Tipo)variable es equivalente a la de static_cast y debe ser evitada.

static_cast puede realizar conversiones entre punteros a clases relacionadas, no solo de una clase derivada a su clase base, sino que también de la clase base a la clase derivada. Esto asegura que al menos las clases son compatibles si el objeto apropiado es convertido, pero no se realiza ningún chequeo de seguridad en tiempo de ejecución para comprobar si el objeto que está siendo convertido es de hecho un objeto completo del tipo destino. Por lo tanto le corresponde al programador la tarea de asegurarse de que la conversión es segura. Por otra parte,  evita la sobrecarga de comprobaciones de seguridad de tipos de dynamic_cast. Veamos un ejemplo:

class CBase {};
class CDerived: public CBase {};
CBase * a = new CBase;
CDerived * b = static_cast<CDerived*>(a);

Este código podría ser válido, aunque b apuntaría a un objeto incompleto de la clase y podría dar lugar a errores en tiempo de ejecución si es desreferenciado. static_cast también puede usarse para realizar cualquier conversión entre variables que no son punteros que pueda llevarse también a cabo implícitamente, como por ejemplo la conversión estándar entre tipos fundamentales de C.

double d=3.14159265;
int i = static_cast<int>(d);

const_cast

const_cast puede ser usado para eliminar o añadir la declaración const a una variable. Ningún otro tipo de conversión de C++ es capaz de esto (ni reinterpret_cast). Es importante apreciar que su comportamiento cuando la variable original ya está declarada como const no está definido. Si lo usas para declarar como const una referencia a algún objeto o puntero que previamente no estaba declarado como const, la operación es segura. Este tipo de conversión es útil cuando sobrecargamos funciones miembro basadas en const. También puede ser usado para añadir const a un objeto, al igual que para llamar a una función miembro sobrecargada.

dynamic_cast

dynamic_cast es casi exclusivamente usado para manejar el polimorfismo. Puedes convertir un puntero o referencia a cualquier tipo polimórfico de cualquier otro tipo de clase (un tipo polimórfico tiene al menos una función virtual, declarada o heredada). No tienes porque usarlo para realizar una conversión hacia abajo. dynamic_cast intentará obtener el objeto deseado y retornarlo si es posible. Si no puede, retornará el valor NULL en el caso de un puntero, o lanzará la excepción std::bad_cast en el caso de una referencia. Por lo tanto, su objetivo es garantizar que el resultado de una conversión es un objeto válido del tipo de objeto que esperamos. Veamos un ejemplo:

class CBase { };
class CDerivada: public CBase { };

CBase b; CBase* pb;
CDerivada d; CDerivada* pd;

pb = dynamic_cast<CBase*>(&d);     // ok: conversión hacia arriba
pd = dynamic_cast<CDerivada*>(&b);  // Falla a no ser que la clase base sea polimórfica (conversión hacia abajo)

dynamic_cast tiene también algunas limitaciones. No funciona si hay múltiples objetos del mismo tipo en el árbol jerárquico y no estás usando herencia virtual y además solo puede funcionar a través de herencia pública (fallará siempre que intentemos usarlo a través de herencia protegida o privada). Sin embargo esto raramente supone un problema, ya que la herencia protegida o privada son utilizadas con mucha menos frecuencia.

Cuando una clase es polimórfica, dynamic_cast realiza un chequeo especial en tiempo de ejecución para asegurar que la expresión da lugar a un objeto completo válido de la clase solicitada:

#include <iostream>
#include <exception>
using namespace std;

class CBase { virtual void dummy() {} };
class CDerived: public CBase { int a; };

int main () {
  try {
    CBase * pba = new CDerived;
    CBase * pbb = new CBase;
    CDerived * pd;

    pd = dynamic_cast<CDerived*>(pba);
    if (pd==0) cout << "Null pointer on first type-cast" << endl;

    pd = dynamic_cast<CDerived*>(pbb);
    if (pd==0) cout << "Null pointer on second type-cast" << endl;

  } catch (exception& e) {cout << "Exception: " << e.what();}
  return 0;
}

El código de arriba intenta realizar dos conversiones dinámicas a partir de punteros a objetos del tipo CBase* (pba y pbb) a un puntero a objeto del tipo CDerived*, pero solo la primera conversión tiene éxito. Aún cuando ambos punteros son del mismo tipo (CBase*), pba apunta a un objeto del tipo CDerived, mientras que pbb apunta a un objeto del tipo CBase. Por lo tanto, cuando las respectivas conversiones se realizan por medio de dynamic_cast, pba está apuntando a un objeto completo de la clase CDerived, mientras que pbb está apuntando a un objeto de la clase CBase, que es un objeto incompleto de la clase CDerived.

Cuando dynamic_cast no puede convertir un puntero debido a que no es un objeto completo de la clase requerida (cómo ocurre en la segunda conversión del ejemplo anterior) devuelve un puntero a NULL para indicar el fallo. Si dynamic_cast es usado para convertir una referencia y la conversión no es posible, en su lugar se lanza una excepción del tipo bad_cast.

dynamic_cast también puede convertir punteros nulos incluso entre puntero a clases no relacionada, y también puede convertir punteros de un tipo a punteros void (void *).

reinterpret_cast

reinterpret_cast es el tipo de conversión más peligroso, y debe usarse con moderación. Convierte un tipo directamente en otro – como al realizar la conversión de un valor de un tipo de puntero a otro, o almacenar un puntero en un int, incluso con clases que no estén relacionadas. En gran parte, la única garantía que obtienes usando este tipo de conversión es que si vuelves a convertir el resultado obtenido mediante esta conversión para obtener el tipo original obtendrás el mismo valor. A parte de eso, deberás usarlo asumiendo tus propios riesgos y tener en cuenta que  reinterpret_cast no puede realizar todo tipo de conversiones; de hecho es bastante limitado.

El resultado de la operación es una simple copia binaria del valor desde un puntero a otro. Todas las conversiones entre punteros son permitidas: no se chequea ni el contenido del puntero ni el propio tipo de puntero.

También puede realizar conversiones entre punteros y tipos enteros. El formato en que dicho valor entero representa a un puntero es específico de la plataforma. La única garantía es que una conversión de puntero a un tipo entero lo suficientemente grande para contenerlo, garantiza que pueda ser de nuevo convertido hacia atrás a un puntero válido.

Las conversiones que pueden ser realizadas mediante reinterpret_cast pero no mediante static_cast y no tienen usos específicos en C++, son operaciones de bajo nivel, cuya interpretación resulta en código que es generalmente específico del sistema y por lo tanto no portable. Por ejemplo:

class A {};
class B {};
A * a = new A;
B * b = reinterpret_cast<B*>(a);

La sintaxis de este código es válida en C++, aunque no tiene mucho sentido ya que tenemos un puntero que apunta a un objeto de una clase incompatible y por lo tanto desreferenciarlo es inseguro.

Conversiones de C

Una conversión de C ((Tipo)objeto o tipo(objeto)) está definida según lo primero que suceda de lo siguiente:

  • const_cast
  • static_cast
  • static_cast, después const_cast
  • reinterpret_cast
  • reinterpret_cast, después const_cast
La funcionalidad de este tipo de operadores de conversión es suficiente para la mayoría de las necesidades con tipos de datos fundamentales. Sin embargo, estos operadores pueden ser aplicados indiscriminadamente sobre clases y punteros a clases, lo que lleva a un código que a pesar de ser sintácticamente correcto puede causar errores en tiempo de ejecución. Veamos un ejemplo sencillo donde se puede observar este hecho:
// Ejemplo extraído de http://www.cplusplus.com/doc/tutorial/typecasting/
// class type-casting
#include <iostream>
using namespace std;

class CDummy {
    float i,j;
};

class CAddition {
	int x,y;
  public:
	CAddition (int a, int b) { x=a; y=b; }
	int result() { return x+y;}
};

int main () {
  CDummy d;
  CAddition * padd;
  padd = (CAddition*) &d;
  cout << padd->result();
  return 0;
}

El programa declara un puntero a CAddition, pero después le asigna a este una referencia a un objeto de otro tipo incompatible usando la conversión explícita de la línea 20. Este tipo de conversiones permiten convertir cualquier puntero en un puntero de otro tipo, independientemente de los tipos a los que se apuntan. La subsiguiente llamada al miembro result producirá bien un error en tiempo de ejecución o un resultado inesperado.

typeid

Para comprobar el tipo de una expresión tenemos la funcionalidad typeid. Este operador retorna una referencia a un objeto constante de tipo type_info que está definida en la cabecera . Este valor retornado puede ser comparado con otros usando los operadores == y != o puede servir para obtener una secuencia de caracteres que representan al tipo de dato o nombre de clase usando su miembro name().

#include <iostream>
#include <typeinfo>
using namespace std;

int main () {
  int * a,b;
  a=0; b=0;
  if (typeid(a) != typeid(b))
  {
    cout << "a and b are of different types:\n";
    cout << "a is: " << typeid(a).name() << '\n';
    cout << "b is: " << typeid(b).name() << '\n';
  }
  return 0;
}

Webs de referencia:

GD Star Rating
loading...
C/C++ : Tipos de cast, 7.8 out of 10 based on 4 ratings
Share
  1. Sin comentarios aún.
  1. Sin trackbacks aún.