¡Nuevo!  por fin disponible la versión 5 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.9.9c  El operador dynamic_cast

Nota: El operador dynamic_cast se refiere a conversiones de punteros y refencias a clases, por lo que su estudio exige un buen conocimiento previo de muchos conceptos relacionados con ellas. Recomendamos al estudiante continuar con el estudio de las clases ( 4.11) antes de volver a este operador concreto.

§1  Sinopsis

Como se ha indicado, este operador está reservado para conversiones de/hacia punteros y referencias a clases, aunque exige que los punteros y referencias se refieran a clases de la misma jerarquía. De no cumplirse este condición, la conversión es imposible y según los casos, el operador produce un resultado nulo o lanza una excepción.

Para entender los conceptos involucrados se hacen necesarias algunas puntualizaciones semánticas previas; recordemos que cuando se trata de clases emparentadas (pertenecientes a una jerarquía), hay que distinguir los casos en que las conversiones ("cast") entre punteros o referencias se realizan en sentido descendente ("Down") desde las clases-base hacia las derivadas, o ascendente ("Up"), desde las clases-derivada hacia las superclases [1]. El primero es el sentido desde los antepasados a los descendientes; el segundo desde los descendientes a los ancestros. Las conversiones en sentido descendente se denominan downcast, y las contrarias upcast. Finalmente, cuando la conversión se da entre clases hermanas, se denomina crosscast (modelado de cruce) [2].

§2  Sintaxis

dynamic_cast<T> (arg);

§3  Comentario

  El operando <T>, denominado tipo, determina el resultado de la operación, y debe ser un puntero ( 4.2.1f) o referencia ( 4.2.3) a una clase definida, o un puntero genérico void* ( 4.2.1d).

  El operando arg recibe el nombre de argumento, es el tipo que se quiere convertir, y debe ser una expresión que se resuelva en un puntero o una referencia (este operador no se puede aplicar a otros tipos).

El resultado del operador es un puntero o referencia del mismo valor que arg, apuntando o referenciando al mismo objeto, pero del tipo expresado por <T>. Este operador se utiliza principalmente para realizar asignaciones que de otro modo no serían legales por diferencia de tipos entre el Lvalue y el Rvalue.

  Para utilizar este operador es necesaria la capacidad RTTI (Runtime type identification 4.9.14).; Así pues, los ejemplos en los que aparezca el operador dynamic_cast deben ser compilados con la opción -RT [4]

§4  Ejemplos

class Clase1 { ... } c1, *c1ptr;
class Clase2 { ... } c2, *c2ptr = &c2, &ref2 = c2;
...
c1ptr = dynamic_cast<Clase1*>(c2ptr);          // L.4:
Clase1& ref1  = dynamic_cast<Clase1&>(ref2);   // L.5:


§5  C++ exige que el donwcast o crosscast se realicen desde una clase polimórfica ( 4.11.8), de modo que arg debe ser de este tipo [3]. Sin embargo, el resultado de este modelado puede ser aplicado a un puntero o referencia a clase no polimórfica.

En el ejemplo anterior, si Clase2 no fuese polimórfica (no tuviese una función virtual), en L4 y L.5 se obtendrían sendos errores:

Type 'Clase2' is not a defined class with virtual functions in...

pero Clase1 no tiene que serlo necesariamente para poder realizar las asignaciones anteriores.


§6  Si <T> es un puntero genérico void*, el argumento arg debe ser también un puntero. En este caso, el puntero resultante puede acceder a cualquier elemento de la clase que sea el elemento más derivado de la jerarquía. Tal clase no puede ser clase-base para ninguna otra.


§7  Las conversiones de una clase derivada a clase-base (upcast) o de una clase derivada a otra (crosscast), se realiza como sigue: si T es un puntero y ptr es un puntero a una clase no-básica de una jerarquía, el resultado es un puntero a la subclase única. Las referencias son tratadas de forma similar: si T es una referencia y ptr es una referencia a una clase no básica, el resultado es una referencia a la subclase única.

§8  La convesión upcast, de una clase derivada a una clase-base se resuelve en tiempo de compilación. Por contra, la conversión downcast, de una clase-base a clase-derivada, o a través de una jerarquía de clases, es resuelta en tiempo de ejecución. Es decir:

a.-  Conversiones en tiempo de compilación:

subclase*   superclase*

subclase&  superclase&

b.-  Conversiones en tiempo de ejecución:

subclase*   superclase*

subclase&  superclase&

El resultado es que, en caso de haberlos, los errores de los modelados a. serán anunciados por el compilador. En cambio, los del tipo b. solo serán detectados en ejecución.


§9  Si en la conversión de un puntero, el operador dynamic_cast< T > (ptr) tiene éxito, proporciona un puntero señalando al mismo objeto que el argumento ptr, pero del tipo T requerido. En cambio, si la conversión falla, el puntero obtenido es del tipo T deseado, pero se le asigna cero. En otras palabras, si el modelado de un puntero falla, se obtiene el puntero nulo ( 4.2.1). Por contra, si falla la conversión a una referencia, se lanza la excepción bad_cast.

  Lo anterior significa que los modelados de este tipo con punteros deben ser comprobados siempre explícitamente para verificar que ptr != 0 (ver líneas 16 y 22 del ejemplo ). En cambio, para verificar el éxito del modelado de referencias basta el mecanismo de excepciones ( 1.6).

§10  Ejemplo

En el programa que sigue se realizan modelados a través de toda la jerarquía. Primero de realiza el downcast de un puntero desde la clase base a la más derivada, después se realiza un upcast para volver a otra base superior.

#include <iostream.h>
#include <typeinfo.h>

class Base1 {
   virtual void f(void) { /* ... */ } // L.5:
};

class Base2 { };
class Derived : public Base1, public Base2 { };

int main(void) {                      // ==========================
  try {
    Derived d, *pd;
    Base1* b1 = &d;                    // L.14:
    pd = dynamic_cast<Derived*>(b1);  // L.15:
    if (pd != 0) {
      cout << "El puntero pd es tipo: " << typeid(pd).name() << endl;
    }
    else throw bad_cast();            // L.19:
    Base2* b2;
    b2 = dynamic_cast<Base2*>(b1);    // L.21:
    if (b2 != 0) {
      cout << "El puntero b2 es tipo: " << typeid(b2).name() << endl;
    }
    else throw bad_cast();            // L.25:
  }
  catch (bad_cast) {                  // L.27:
    cout << "Falla el modelado dynamic_cast" << endl;
    return 1;
  }
  catch (...) {                        // L.31:
    cout << "Error en el mecanismo de excepciones" << endl;
    return 1;
  }
  return 0;                            // L.35:
}

Salida:

El puntero pd es tipo: Derived *
El puntero b2 es tipo: Base2 *

Comentario

En L.5 declaramos una función virtual en la superclase Base1, dado que para que funcione correctamente el mecanismo RTTI, la clase base debe ser polimórfica.

En L.13 creamos una instancia d de la clase Derived y un puntero pd a dicha clase.

En L.14 a un puntero-a-clase-base (b1) le asignamos la dirección de una instancia (objeto) de clase derivada. Esto es perfectamente lícito ( 4.2.1f)

En L.15 al puntero pd (a clase derivada) se le asigna la dirección dirección señalada por b1. La dirección de b1 es correcta (la dirección de una subclase), pero el tipo no lo es, ya que b1 es de tipo Base1*. Para poder formalizar la asignación es necesario realizar previamente un downcast, de forma que b1 pase a ser tipo Derived*.

Las líneas L.16 a L.19 comprueban que el modelado ha tenido éxito. En caso contrario lanzan una excepción bad_cast que es capturada en L.27. Observe que en L.31 se captura cualquier excepción del bloque try precedente, que no haya sido capturada con anterioridad.

En L.21, a  b2 (que es tipo Base2*) se le asigna la dirección señalada por b1. En esta asignación ni la dirección ni el tipo son correctos. En primer lugar, b1 señala a un objeto tipo Derived. Además, b1 es tipo Base1*. En consecuencia, se necesita un modelado al tipo de b2. Este modelado debe recorrer toda la jerarquía de clases, descendiendo (downcast) desde el tipo Base1* a la del tipo más derivado Derived*; después ascendiendo (upcast) desde el tipo derived* al tipo Base2*.

En L.35 se anuncia la finalización normal del programa. En cambio, las terminaciones L.29 y L.33 son terminaciones anormales, por lo que devuelven un valor distinto de cero al SO.

§11  Comprobación de parentesco

Dentro de las limitaciones señaladas (que se realice sobre clases polimórficas), la circunstancia de que el operador dynamic_cast solo funcione entre miembros de una jerarquía, es utilizado a menudo como un recurso para comprobar si dos objetos pertenecen o no a una misma familia.

Por ejemplo, supongamos que tenemos un objeto a pertenecientes a la clases A. Para comprobar si pertenece a una subclase de B (si A deriva de B), podría utilizarse el siguiente código:

if (dynamic_cast<B*>(&a)) {
    /* A es derivada de B */
} else {
    /* A no deriva de B */
}

  Inicio.


[1]  Las palabras "ascendente" y "descentente", se deben a que tradicionalmente, las jerarquías de clases se representan como árboles invertidos, con las clases-base en la parte superior, y las derivadas hacia abajo (ver figura 4.11.2b).

[2]  Lamentándolo mucho, utilizaremos en lo sucesivo los vocablos ingleses por ser más cortos que los españoles.

[3]  La razón es exclusivamente de facilidad técnica para la construcción de compiladores ( Stroustrup TC++PL  §15.4.1).

[4]  En el compilador Borland C++ 5.5 esta opción está activada (ON) por defecto.