42 Castings en C++

Hasta ahora hemos usado sólo el casting que existe en C, que vimos en el capítulo 9. Pero ese tipo de casting no es el único que existe en C++, de hecho, su uso está desaconsejado, ya que el por una parte los paréntesis se usan mucho en C++, además, este tipo de casting realiza conversiones diferentes dependiendo de cada situación. Se recomienda usar uno de los nuevos operadores de C++ diseñados para realizar esta tarea.

C++ dispone de cuatro operadores para realizar castings, algunos de ellos necesarios para realizar conversiones entre objetos de una misma jerarquía de clases.

Operador static_cast<>

La sintaxis de este operador es:

static_cast<tipo> (<expresión>);

Este operador realiza una conversión de tipo durante la compilación del programa, de modo que no crea más código durante la ejecución, descargando esa tarea en el compilador.

Este operador se usa para realizar conversiones de tipo que de otro modo haría el compilador automáticamente, por ejemplo, convertir un puntero a un objeto de una clase derivada a un puntero a una clase base pública:

#include <iostream>
using namespace std;

class Base {
  public:
   Base(int valor) : x(valor) {}
   void Mostrar() { cout << x << endl; }

  protected:
   int x;
};

class Derivada : public Base {
  public:
   Derivada(int ivalor, float fvalor) :
      Base(ivalor), y(fvalor) {}
   void Mostrar() {
      cout << x << ", " << y << endl;
   }

  private:
   float y;
};

int main() {
   Derivada *pDer = new Derivada(10, 23.3);
   Base *pBas;

   pDer->Mostrar(); // Derivada
   pBas = static_cast<Base *> (pDer);
   // pBas = pDer; // Igualmente legal, pero implícito
   pBas->Mostrar(); // Base

   delete pDer;
   return 0;
}

Otro ejemplo es cuando se usan operadores de conversión de tipo, como en el caso del capítulo 35, en lugar de usar el operador de forma implícita, podemos usarlo mediante el operador static_cast:

#include <iostream>
using namespace std;

class Tiempo {
  public:
   Tiempo(int h=0, int m=0) : hora(h), minuto(m) {}

   void Mostrar();
   operator int() {
      return hora*60+minuto;
   }
  private:
   int hora;
   int minuto;
};

void Tiempo::Mostrar() {
   cout << hora << ":" << minuto << endl;
}

int main() {
   Tiempo Ahora(12,24);
   int minutos;

   Ahora.Mostrar();
   minutos = static_cast<int> (Ahora);
   // minutos = Ahora; // Igualmente legal, pero implícito

   cout << minutos << endl;

   return 0;
}

Este operador se usa en los casos en que el programador desea documentar las conversiones de tipo implícitas, con objeto de aclarar que realmente se desean hacer esas conversiones de tipo.

Operador const_cast<>

La sintaxis de este operador es:

const_cast<tipo> (<expresión>);

Se usa para eliminar o añadir los modificadores const y volatile de una expresión.

Por eso, tanto tipo como expresión deben ser del mismo tipo, salvo por los modificadores const o volatile que tengan que aparecer o desaparecer.

Por ejemplo:

#include <iostream>
using namespace std;

int main() {
   const int x = 10;
   int *x_var;

   x_var = const_cast<int*> (&x); // Válido
   // x_var = &x; // Ilegal, el compilador da error
   *x_var = 14;   // Indefinido

   cout << *x_var << ", " << x << endl;

   return 0;
}

En el ejemplo vemos que podemos hacer que un puntero apunte a una constante, e incluso podemos modificar el valor de la variable a la que apunta. Sin embargo, este operador se usa para obtener expresiones donde se necesite añadir o eliminar esos modificadores. Si se intenta modificar el valor de una expresión constante, el resultado es indeterminado.

El ejemplo anterior, compilado en Dev-C++ no modifica el valor de x, lo cual es lógico, ya que x es constante.

Operador reinterpret_cast<>

La sintaxis de este operador es:

reinterpret_cast<tipo> (<expresión>);

Se usa para hacer cambios de tipo a nivel de bits, es decir, el valor de la "expresión" se interpreta como si fuese un objeto del tipo "tipo". Los modificadores const y volatile no se modifican, permanecen igual que en el valor original de "expresión". Este tipo de conversión es peligrosa, desde el punto de vista de la compatibilidad, hay que usarla con cuidado.

Posibles usos son conseguir punteros a variables de distinto tipo al original, por ejemplo:

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

int main() {
   int x = 0x12dc34f2;
   int *pix = &x;
   unsigned char *pcx;

   <b>pcx = reinterpret_cast<unsigned char *> (pix);</b>

   cout << hex << " = "
      << static_cast<unsigned int> (pcx[0]) << ", "
      << static_cast<unsigned int> (pcx[1]) << ", "
      << static_cast<unsigned int> (pcx[2]) << ", "
      << static_cast<unsigned int> (pcx[3]) << endl;

   return 0;
}

La salida tiene esta forma:

12dc34f2 = f2, 34, dc, 12

Operador typeid

La sintaxis de este operador es:

const type_info typeid(<tipo>)
const type_info typeid(<objeto>)

El tipo puede ser cualquiera de los fundamentales, derivados o una clase, estructura o unión. Si se trata de un objeto, también puede ser de cualquier tipo.

El valor de retorno un objeto constante de tipo type_info.

La clase type_info se define en el fichero de cabecera estándar "typeinfo". Tiene la siguiente declaración:

class type_info {
  public:
    virtual ~type_info();

  private:
    type_info& operator=(const type_info&);
    type_info(const type_info&);
    
  protected:
    const char *__name;
    
  protected:
    explicit type_info(const char *__n): __name(__n) { }
    
  public:
    const char* name() const;
    bool before(const type_info& __arg) const;
    bool operator==(const type_info& __arg) const;
    bool operator!=(const type_info& __arg) const;
    ...
};

Nos interesa, concretamente, la función "name", y tal vez, los operadores == y !=.

La función "name" nos permite mostrar el nombre del tipo a que pertenece un objeto, los operadores nos permiten averiguar si dos objetos son del mismo tipo, o clase o si dos clases o tipos son equivalentes, por ejemplo:

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

struct punto3D {
   int x,y,z;
};

union Union {
  int x;
  float z;
  char a;
};

class Clase {
  public:
   Clase() {}
};

typedef int Entero;

int main() {
   int x;
   float y;
   int z[10];
   punto3D punto3d;
   Union uni;
   Clase clase;
   void *pv;
   
   cout << "variable int: " << typeid(x).name() << endl;
   cout << "variable float: " << typeid(y).name() << endl;
   cout << "array de 10 int:" << typeid(z).name() << endl;
   cout << "estructura global: " << typeid(punto3d).name() 
        << endl;
   cout << "unión global: " << typeid(uni).name() << endl;
   cout << "clase global: " << typeid(clase).name() 
        << endl;
   cout << "puntero void: " << typeid(pv).name() << endl;
   cout << "typodef Entero: " << typeid(Entero).name() 
        << endl;
   if(typeid(Entero) == typeid(int)) 
      cout << "int y Entero son el mismo tipo" << endl;
   
   return 0;
}

Ejecutar este código en codepad.

La salida, en Dev-C++, tiene esta forma:

variable int: i
variable float: f
array de 10 int:A10_i
estructura global: 7punto3D
unión global: 5Union
clase global: 5Clase
puntero void: Pv
typodef Entero: i
int y Entero son el mismo tipo

La utilidad es detectar los tipos de objetos durante la ejecución, sobre todo en aplicaciones con frecuente uso de polimorfismo, y que requieran diferentes formas de manejar cada objeto en función de su clase.

Además de usar el operador typeid se puede usar el operador dynamic_cast, que se explica en el siguiente punto.

Operador dynamic_cast<>

La sintaxis de este operador es:

dynamic_cast<tipo> (<objeto>);

Se usa para hacer cambios de tipo durante la ejecución. Y se usa la base de datos formada por las estructuras type_info que vimos antes.

Este operador sólo puede usarse con objetos polimórficos, cualquier intento de aplicarlo a objetos de tipos fundamentales o agregados o de clases no polimórficas dará como resultado un error.

Hay dos modalidades de dynamic_cast, una usa punteros y la otra referencias:

class Base { // Clase base virtual
...
};
 
class Derivada : public Base { // Clase derivada
...
};

// Modalidad de puntero:
Derivada *p = dynamic_cast<Derivada *> (&Base);
// Modalidad de referencia:
Derivada &refd = dynamic_cast<Derivada &> (Base);

Lo que el programa intentará hacer, durante la ejecución, es obtener bien un puntero o bien una referencia a un objeto de cierta clase base en forma de clase derivada, pero puede que se consiga, o puede que no.

Esto es, en cierto modo, lo contrario a lo que hacíamos al usar polimorfismo. En lugar de obtener un puntero o referencia a la clase base a partir de la derivada, haremos lo contrario: intentar obtener un puntero o referencia a la clase derivada a partir de la clase base.

Volvamos a nuestro ejemplo de Personas.

Vamos a añadir un miembro "sueldo" a la clase "Empleado", y la modificaremos para poder inicializar y leer ese dato.

También crearemos una función "LeerSueldo" que aceptará como parámetro un puntero a un objeto de la clase "Persona":

#include <iostream>
#include <cstring>
using namespace std;
 
class Persona { // Virtual
  public:
   Persona(char *n) { strcpy(nombre, n); }
   virtual void VerNombre() = 0; // Virtual pura
  protected:
   char nombre[30];
};

class Empleado : public Persona {
  public:
   Empleado(char *n, float s) : Persona(n), sueldo(s) {}
   void VerNombre() { 
      cout << "Emp: " << nombre << endl; 
   }
   void VerSueldo() {
      cout << "Salario: " << sueldo << endl; 
   }   
  private:
   float sueldo;
};

class Estudiante : public Persona {
  public:
   Estudiante(char *n) : Persona(n) {}
   void VerNombre() { 
      cout << "Est: " << nombre << endl;
   }
};

void VerSueldo(Persona *p) {
   if(Empleado *pEmp = dynamic_cast<Empleado *> (p))
      pEmp->VerSueldo();
   else
      cout << "No tiene salario." << endl;
}

int main() {
   Persona *Pepito = new Estudiante("Jose");
   Persona *Carlos = new Empleado("Carlos", 1000.0);

   Carlos->VerNombre();
   VerSueldo(Carlos);

   Pepito->VerNombre();
   VerSueldo(Pepito);

   delete Pepito;
   delete Carlos;
   
   return 0;
}

La función "VerSueldo" recibe un puntero a un objeto de la clase base virtual "Persona". Pero a priori no sabemos si el objeto original era un empleado o un estudiante, de modo que no podemos prever si tendrá o no sueldo. Para averiguarlo hacemos un casting dinámico a la clase derivada "Empleado", y si tenemos éxito es que se trata efectivamente de un empleado y mostramos el salario. Si fracasamos, mostramos un mensaje de error.

Pero esto es válido sólo con punteros, si intentamos hacerlo con referencias tendremos un serio problema, ya que no es posible declarar referencias indefinidas. Esto quiere decir que, por una parte, no podemos usar la expresión del casting como una condición en una sentencia if. Por otra parte, si el casting fracasa, se producirá una excepción, ya que se asignará un valor nulo a una referencia durante la ejecución:

void VerSueldo(Persona &p) {
   Empleado rEmp = dynamic_cast<Empleado &> p;
...

Por lo tanto tendremos que usar manejo de excepciones (que es el tema del siguiente capítulo), para usar referencias con el casting dinámico:

void VerSueldo(Persona &p) {
   try {
      Empleado rEmp = dynamic_cast<Empleado &> p;
      rEmp.VerSueldo();
   }
   catch (std::bad_cast) {
      cout << "No tiene salario." << endl;
   }
}

Castings cruzados

Cuando tenemos una clase producto de una derivación múltiple, es posible obtener punteros o referencias a una clase base a partir de objetos o punteros a objetos de otra clase base, eso sí, es necesario usar polimorfismo, no podemos usar un objeto de una clase base para obtener otra. Por ejemplo:

#include <iostream>
using namespace std;

class ClaseA {
  public:
   ClaseA(int x) : valorA(x) {}
   void Mostrar() {
      cout << valorA << endl;
   }
   virtual void nada() {} // Forzar polimorfismo
  private:
   int valorA;
};

class ClaseB {
  public:
   ClaseB(float x) : valorB(x) {}
   void Mostrar() {
      cout << valorB << endl;
   }
   
  private:
   float valorB;
};

class ClaseD : public ClaseA, public ClaseB {
  public:
   ClaseD(int x, float y, char c) : 
      ClaseA(x), ClaseB(y), valorD(c) {}
   void Mostrar() {
      cout << valorD << endl;
   }
   
  private:
   char valorD;
};

int main() {
   ClaseA *cA = new ClaseD(10,15.3,'a');
   ClaseB *cB = dynamic_cast<ClaseB*> (cA);
   
   cA->Mostrar();
   cB->Mostrar();
   
   return 0;
}