cpp-static-dynamic-const-cast

Conversores static_cast, dynamic_cast y const_cast en C++

En C++ los casts específicos son formas más controladas de realizar conversiones entre tipos. Estos son static_cast, dynamic_cast, const_cast y reinterpret_cast.

Cada uno tiene su propósito específico

  • static_cast: Para conversiones entre tipos relacionados sin polimorfismo
  • dynamic_cast: Para conversiones seguras en jerarquías de clases polimórficas
  • const_cast: Para eliminar o añadir const (con precaución ⚠️)
  • reinterpret_cast: Conversiones de bajo nivel entre tipos no relacionados, como punteros y enteros (con mucha precaución ❗)

Estos permiten gestionar de manera más segura la conversión de tipos, frente al operador “clásico” de conversión (tipoDato)objeto (sobre todo al trabajar con jerarquías de clases y herencia).

Aunque el operador (tipoDato) es más rápido de escribir, puede ser peligroso porque no realiza ningún tipo de verificación de seguridad, lo que puede resultar en error.

Conversión con static_cast

static_cast se usa para conversiones entre tipos relacionados que pueden ser determinadas en tiempo de compilación. Este tipo de cast es muy útil para convertir entre tipos numéricos, así como en jerarquías de clases sin polimorfismo.

Ejemplo con tipos numéricos:

double numeroDecimal = 9.99;
int numeroEntero = static_cast<int>(numeroDecimal); // Conversión explícita

En este caso, se convierte el valor double a un int, perdiendo la parte decimal del número de manera consciente. Al usar static_cast, se asegura una conversión clara y el programador es responsable de esta pérdida de precisión.

Ejemplo con punteros en jerarquías de clases:

class Base {};
class Derivada : public Base {};

Base* basePtr = new Derivada();
Derivada* derivadaPtr = static_cast<Derivada*>(basePtr); // Conversión válida

Aquí, static_cast convierte un puntero de clase base (Base*) a un puntero de una clase derivada (Derivada*). La conversión es válida porque basePtr en realidad apunta a un objeto de la clase derivada.

No se debe usar static_cast cuando hay polimorfismo involucrado, ya que no comprueba si la conversión es segura en tiempo de ejecución. En su lugar, debes utilizar dynamic_cast en estos casos.

Conversión con dynamic_cast

dynamic_cast se utiliza principalmente cuando trabajamos con jerarquías de clases que tienen al menos una función virtual (es decir, clases con polimorfismo).

Este tipo de cast es el único que verifica la validez de la conversión en tiempo de ejecución, devolviendo nullptr si la conversión no es posible.

Ejemplo de conversión segura en jerarquías polimórficas:

class Base {
public:
    virtual void metodo() {}  // Clase polimórfica porque tiene al menos una función virtual
};

class Derivada : public Base {
public:
    void metodo() override {}
};

Base* basePtr = new Derivada();
Derivada* derivadaPtr = dynamic_cast<Derivada*>(basePtr); // Conversión válida

En este ejemplo,

  • dynamic_cast convierte correctamente el puntero basePtr (de tipo Base*) a un puntero Derivada*.
  • Si basePtr no apuntara a un objeto de la clase Derivada, dynamic_cast devolvería nullptr (evitando errores como accesos ilegales a memoria)

Ejemplo con conversión fallida:

Base* basePtr = new Base();
Derivada* derivadaPtr = dynamic_cast<Derivada*>(basePtr);

if (derivadaPtr == nullptr) {
    std::cout << "La conversión no fue exitosa." << std::endl;
}

Aquí, la conversión falla porque basePtr no apunta a un objeto de tipo Derivada, y el puntero derivadaPtr es nullptr.

  • No uses dynamic_cast si puedes determinar la conversión en tiempo de compilación (usa static_cast en esos casos).

Conversión con const_cast

const_cast se utiliza para eliminar o añadir el calificador const a un objeto.

Aunque parece útil, modificar un objeto que fue declarado originalmente como const puede resultar en comportamiento indefinido, así que se debe usar con precaución.

Ejemplo de eliminación del calificador const:

const int numeroConstante = 42;
int* ptr = const_cast<int*>(&numeroConstante); // Elimina const
*ptr = 100;  // Comportamiento indefinido: modificar una variable const

En este caso,

  • const_cast se usa para eliminar el calificador const de numeroConstante

Aunque el compilador lo permite, modificar un valor declarado como const puede llevar a resultados impredecibles

Ejemplo de uso legítimo de const_cast:

const_cast también puede ser útil cuando se trabaja con funciones que no marcan correctamente sus parámetros como const.

Por ejemplo, si tienes una función de una API que toma un puntero no const, pero sabes que no modifica el valor:

void funcionQueNoModifica(int* ptr) {
    // No modifica el valor
}

const int numero = 50;
funcionQueNoModifica(const_cast<int*>(&numero));  // Conversión legítima
  • Evita modificar objetos que fueron originalmente declarados como const
  • Es más, en general evita usarlo 😉

Conversión con reinterpret_cast

reinterpret_cast es el cast más peligroso y se utiliza para realizar conversiones de bajo nivel entre tipos de punteros o entre punteros y tipos de datos enteros.

A diferencia de los otros tipos de cast, no realiza ningún tipo de verificación de seguridad y simplemente “reinterpretará” los bits del objeto en el nuevo tipo.

Básicamente, reinterpret_cast es similar al cast clásico (tipoDato).

Ejemplo de conversión de punteros:

int numero = 65;
char* letraPtr = reinterpret_cast<char*>(&numero);
std::cout << *letraPtr << std::endl;  // Imprime 'A', que es el carácter ASCII para 65

En este ejemplo:

  • reinterpret_cast convierte la dirección de memoria del entero numero en un puntero char*.
  • Esto permite interpretar los primeros bytes de la representación binaria de numero como un carácter ASCII.

Ejemplo de conversión entre puntero e int:

int numero = 42;
uintptr_t ptrAsInt = reinterpret_cast<uintptr_t>(&numero);  // Convierte un puntero en un entero sin signo
std::cout << ptrAsInt << std::endl;

Aquí, el puntero a numero es convertido a un número entero sin signo de tipo uintptr_t (un tipo especial que puede almacenar direcciones de memoria). Esto puede ser útil cuando necesitas trabajar con direcciones de memoria en un contexto específico, pero en general, no se recomienda abusar de este tipo de conversiones.

  • Evita usarlo para cualquier tipo de conversión entre clases o tipos de datos que no estén directamente relacionados
  • Es más, evita mucho usarlo