Dynamic_cast y static_cast en C ++

155

Estoy bastante confundido con la dynamic_castpalabra clave en C ++.

struct A {
    virtual void f() { }
};
struct B : public A { };
struct C { };

void f () {
    A a;
    B b;

    A* ap = &b;
    B* b1 = dynamic_cast<B*> (&a);  // NULL, because 'a' is not a 'B'
    B* b2 = dynamic_cast<B*> (ap);  // 'b'
    C* c = dynamic_cast<C*> (ap);   // NULL.

    A& ar = dynamic_cast<A&> (*ap); // Ok.
    B& br = dynamic_cast<B&> (*ap); // Ok.
    C& cr = dynamic_cast<C&> (*ap); // std::bad_cast
}

la definición dice:

La dynamic_castpalabra clave arroja un dato de un puntero o tipo de referencia a otro, realizando una verificación de tiempo de ejecución para garantizar la validez del molde.

¿Podemos escribir un equivalente de dynamic_castC ++ en C para poder entender mejor las cosas?

Vijay
fuente
1
Si desea tener una buena idea de cómo dynamic_cast<>funciona detrás de escena (o cuánto funciona C ++), un buen libro (que también es bastante fácil de leer para algo tan técnico) es "Inside the C ++ Object Model" de Lippman. También los libros "Diseño y evolución de C ++" y "El lenguaje de programación C ++" de Stroustrup son buenos recursos, pero el libro de Lippman está dedicado a cómo funciona C ++ "detrás de escena".
Michael Burr
¿Qué significa el comentario en la línea B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to b¿o que?
LRDPRDX
@BogdanSikach ¿Qué pregunta es esa? Simplemente significa que la AP ahora es un tipo de clase B

Respuestas:

282

Aquí hay un resumen static_cast<>y dynamic_cast<>específicamente en lo que respecta a los punteros. Este es solo un resumen de 101 niveles, no cubre todas las complejidades.

static_cast <Tipo *> (ptr)

Esto toma el puntero ptre intenta convertirlo de manera segura en un puntero de tipo Type*. Este reparto se realiza en tiempo de compilación. Solo realizará la conversión si los tipos de tipos están relacionados. Si los tipos no están relacionados, obtendrá un error de compilación. Por ejemplo:

class B {};
class D : public B {};
class X {};

int main()
{
  D* d = new D;
  B* b = static_cast<B*>(d); // this works
  X* x = static_cast<X*>(d); // ERROR - Won't compile
  return 0;
}

Dynamic_cast <Tipo *> (ptr)

Esto intenta nuevamente tomar el puntero ptry convertirlo de manera segura en un puntero de tipo Type*. Pero este reparto se ejecuta en tiempo de ejecución, no en tiempo de compilación. Debido a que este es un lanzamiento en tiempo de ejecución, es útil especialmente cuando se combina con clases polimórficas. De hecho, en casos certian las clases deben ser polimórficas para que el reparto sea legal.

Los moldes pueden ir en una de dos direcciones: de base a derivada (B2D) o de derivada a base (D2B). Es lo suficientemente simple como para ver cómo los moldes D2B funcionarían en tiempo de ejecución. O ptrse derivaba de Typeo no. En el caso de D2B dynamic_cast <> s, las reglas son simples. Puede intentar lanzar cualquier cosa a otra cosa, y si ptrde hecho se derivó Type, obtendrá un Type*puntero de regreso dynamic_cast. De lo contrario, obtendrá un puntero NULL.

Pero los modelos B2D son un poco más complicados. Considere el siguiente código:

#include <iostream>
using namespace std;

class Base
{
public:
    virtual void DoIt() = 0;    // pure virtual
    virtual ~Base() {};
};

class Foo : public Base
{
public:
    virtual void DoIt() { cout << "Foo"; }; 
    void FooIt() { cout << "Fooing It..."; }
};

class Bar : public Base
{
public :
    virtual void DoIt() { cout << "Bar"; }
    void BarIt() { cout << "baring It..."; }
};

Base* CreateRandom()
{
    if( (rand()%2) == 0 )
        return new Foo;
    else
        return new Bar;
}


int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

            base->DoIt();

        Bar* bar = (Bar*)base;
        bar->BarIt();
    }
  return 0;
}

main()no puedo decir qué tipo de objeto CreateRandom()volverá, por lo que el reparto de estilo C Bar* bar = (Bar*)base;definitivamente no es seguro para escribir. ¿Cómo puedes arreglar esto? Una forma sería agregar una función como bool AreYouABar() const = 0;a la clase base y regresar truedesde Bary falsedesde Foo. Pero hay otra forma: use dynamic_cast<>:

int main()
{
    for( int n = 0; n < 10; ++n )
    {
        Base* base = CreateRandom();

        base->DoIt();

        Bar* bar = dynamic_cast<Bar*>(base);
        Foo* foo = dynamic_cast<Foo*>(base);
        if( bar )
            bar->BarIt();
        if( foo )
            foo->FooIt();
    }
  return 0;

}

Los lanzamientos se ejecutan en tiempo de ejecución y funcionan consultando el objeto (no hay que preocuparse por cómo hacerlo por ahora), preguntándole si es del tipo que estamos buscando. Si es así, dynamic_cast<Type*>devuelve un puntero; de lo contrario, devuelve NULL.

Para que esta conversión de base a derivada funcione dynamic_cast<>, Base, Foo y Bar deben ser lo que el Estándar llama tipos polimórficos . Para ser un tipo polimórfico, su clase debe tener al menos una virtualfunción. Si sus clases no son tipos polimórficos, el uso de base a derivada de dynamic_castno se compilará. Ejemplo:

class Base {};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // ERROR - Won't compile

    return 0;
}

Agregar una función virtual a la base, como un dtor virtual, hará que los tipos polimórficos Base y Der:

class Base 
{
public:
    virtual ~Base(){};
};
class Der : public Base {};


int main()
{
    Base* base = new Der;
    Der* der = dynamic_cast<Der*>(base); // OK

    return 0;
}
John Dibling
fuente
9
¿Por qué el compilador se queja de eso en primer lugar? y no lo hacemos cuando proporcionamos solo un dctor virtual solo para base?
Rika
55
Cabe señalar que si lo hace Base* base = new Base;, dynamic_cast<Foo*>(base)lo será NULL.
Yay295
2
@ Coderx7 dynamic_cast necesita información de tipo de tiempo de ejecución (RTTI) que está disponible solo para clases que son polimórficas, es decir, clases con al menos un método virtual.
Elvorfirilmathredia
@ Yay295 ¿Por qué dynamic_cast<Foo*>(base)es nulo en caso de a Base* base = new Base;?
MuneshSingh
3
@munesh Porque baseno es un Foo. Un Basepuntero puede apuntar a un Foo, pero sigue siendo un Foo, por lo que un reparto dinámico funcionará. Si lo hace Base* base = new Base, basees a Base, no a Foo, por lo que no puede convertirlo dinámicamente en a Foo.
Yay295
20

A menos que esté implementando su propio RTTI enrollado a mano (y evitando el sistema), no es posible implementarlo dynamic_castdirectamente en el código de nivel de usuario C ++. dynamic_castestá muy vinculado al sistema RTTI de la implementación de C ++.

Pero, para ayudarlo a comprender RTTI (y por lo tanto dynamic_cast) más, debe leer el <typeinfo>encabezado y el typeidoperador. Esto devuelve la información de tipo correspondiente al objeto que tiene a mano, y puede consultar varias cosas (limitadas) de estos objetos de información de tipo.

Chris Jester-Young
fuente
Te diría a Wikipedia, pero sus artículos sobre RTTI y dynamic_castson muy escasos. :-P Solo juega con él hasta que te acostumbres. :-)
Chris Jester-Young
10

Más que el código en C, creo que una definición en inglés podría ser suficiente:

Dada una clase Base de la cual hay una clase derivada Derivada, dynamic_castconvertirá un puntero Base en un puntero Derivado si y solo si el objeto real al que apunta es de hecho un objeto Derivado.

class Base { virtual ~Base() {} };
class Derived : public Base {};
class Derived2 : public Base {};
class ReDerived : public Derived {};

void test( Base & base )
{
   dynamic_cast<Derived&>(base);
}

int main() {
   Base b;
   Derived d;
   Derived2 d2;
   ReDerived rd;

   test( b );   // throw: b is not a Derived object
   test( d );   // ok
   test( d2 );  // throw: d2 is not a Derived object
   test( rd );  // ok: rd is a ReDerived, and thus a derived object
}

En el ejemplo, la llamada a testenlaza diferentes objetos a una referencia Base. Internamente, la referencia se descarta a una referencia de Deriveduna forma segura: el downcast tendrá éxito solo para aquellos casos en los que el objeto referenciado sea realmente una instancia de Derived.

David Rodríguez - dribeas
fuente
2
Creo que es mejor aclarar que los ejemplos compartidos anteriormente funcionarán en base a suposiciones si las clases son solo polimórficas, es decir, al menos la clase Base tiene al menos un método virtual.
irsis
1
Esto fallará porque las clases no son tipos polimórficos.
username_4567
4

Lo siguiente no está realmente cerca de lo que obtienes de C ++ dynamic_casten términos de verificación de tipo, pero tal vez te ayudará a comprender un poco mejor su propósito:

struct Animal // Would be a base class in C++
{
    enum Type { Dog, Cat };
    Type type;
};

Animal * make_dog()
{
   Animal * dog = new Animal;
   dog->type = Animal::Dog;
   return dog;
}
Animal * make_cat()
{
   Animal * cat = new Animal;
   cat->type = Animal::Cat;
   return cat;
}

Animal * dyn_cast(AnimalType type, Animal * animal)
{
    if(animal->type == type)
        return animal;
    return 0;
}

void bark(Animal * dog)
{
    assert(dog->type == Animal::Dog);

    // make "dog" bark
}

int main()
{
    Animal * animal;
    if(rand() % 2)
        animal = make_dog();
    else
        animal = make_cat();

    // At this point we have no idea what kind of animal we have
    // so we use dyn_cast to see if it's a dog

    if(dyn_cast(Animal::Dog, animal))
    {
        bark(animal); // we are sure the call is safe
    }

    delete animal;
}
Manuel
fuente
3

A dynamic_castrealiza una verificación de tipo usando RTTI . Si falla, le arrojará una excepción (si le dio una referencia) o NULL si le dio un puntero.

f4.
fuente
2

Primero, para describir la conversión dinámica en términos de C, tenemos que representar las clases en C. Las clases con funciones virtuales usan un "VTABLE" de punteros a las funciones virtuales. Los comentarios son C ++. Siéntase libre de formatear y corregir errores de compilación ...

// class A { public: int data; virtual int GetData(){return data;} };
typedef struct A { void**vtable; int data;} A;
int AGetData(A*this){ return this->data; }
void * Avtable[] = { (void*)AGetData };
A * newA() { A*res = malloc(sizeof(A)); res->vtable = Avtable; return res; }

// class B : public class A { public: int moredata; virtual int GetData(){return data+1;} }
typedef struct B { void**vtable; int data; int moredata; } B;
int BGetData(B*this){ return this->data + 1; }
void * Bvtable[] = { (void*)BGetData };
B * newB() { B*res = malloc(sizeof(B)); res->vtable = Bvtable; return res; }

// int temp = ptr->GetData();
int temp = ((int(*)())ptr->vtable[0])();

Entonces un elenco dinámico es algo así como:

// A * ptr = new B();
A * ptr = (A*) newB();
// B * aB = dynamic_cast<B>(ptr);
B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );
David Rayna
fuente
1
La pregunta inicial fue "¿Podemos escribir un equivalente de dynamic_cast de C ++ en C".
David Rayna
1

No hay clases en C, por lo que es imposible escribir dynamic_cast en ese lenguaje. Las estructuras C no tienen métodos (como resultado, no tienen métodos virtuales), por lo que no hay nada "dinámico" en ellas.

a1ex07
fuente
1

No, no fácilmente. El compilador asigna una identidad única a cada clase, esa información es referenciada por cada instancia de objeto, y eso es lo que se inspecciona en tiempo de ejecución para determinar si una conversión dinámica es legal. Puede crear una clase base estándar con esta información y operadores para hacer la inspección de tiempo de ejecución en esa clase base, luego cualquier clase derivada informaría a la clase base de su lugar en la jerarquía de clases y cualquier instancia de esas clases sería ejecutable en tiempo de ejecución a través de sus operaciones

editar

Aquí hay una implementación que demuestra una técnica. No estoy afirmando que el compilador use algo como esto, pero creo que demuestra los conceptos:

class SafeCastableBase
{
public:
    typedef long TypeID;
    static TypeID s_nextTypeID;
    static TypeID GetNextTypeID()
    {
        return s_nextTypeID++;
    }
    static TypeID GetTypeID()
    {
        return 0;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return false; }
        return true;
    }
    template <class Target>
    static Target *SafeCast(SafeCastableBase *pSource)
    {
        if (pSource->CanCastTo(Target::GetTypeID()))
        {
            return (Target*)pSource;
        }
        return NULL;
    }
};
SafeCastableBase::TypeID SafeCastableBase::s_nextTypeID = 1;

class TypeIDInitializer
{
public:
    TypeIDInitializer(SafeCastableBase::TypeID *pTypeID)
    {
        *pTypeID = SafeCastableBase::GetNextTypeID();
    }
};

class ChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID ChildCastable::s_typeID;

TypeIDInitializer ChildCastableInitializer(&ChildCastable::s_typeID);

class PeerChildCastable : public SafeCastableBase
{
public:
    static TypeID s_typeID;
    static TypeID GetTypeID()
    {
        return s_typeID;
    }
    virtual bool CanCastTo(TypeID id)
    {
        if (GetTypeID() != id) { return SafeCastableBase::CanCastTo(id); }
        return true;
    }
};
SafeCastableBase::TypeID PeerChildCastable::s_typeID;

TypeIDInitializer PeerChildCastableInitializer(&PeerChildCastable::s_typeID);

int _tmain(int argc, _TCHAR* argv[])
{
    ChildCastable *pChild = new ChildCastable();
    SafeCastableBase *pBase = new SafeCastableBase();
    PeerChildCastable *pPeerChild = new PeerChildCastable();
    ChildCastable *pSameChild = SafeCastableBase::SafeCast<ChildCastable>(pChild);
    SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast<SafeCastableBase>(pChild);
    ChildCastable *pNullDownCast = SafeCastableBase::SafeCast<ChildCastable>(pBase);
    SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast<SafeCastableBase>(pPeerChild);
    ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast<ChildCastable>(pPeerChild);
    return 0;
}
David Gladfelter
fuente
0

static_cast< Type* >(ptr)

static_cast en C ++ se puede usar en escenarios donde se puede verificar todo tipo de conversión en tiempo de compilación .

dynamic_cast< Type* >(ptr)

Dynamic_cast en C ++ se puede utilizar para realizar el tipo de conversión segura . dynamic_cast es un polimorfismo en tiempo de ejecución. El operador dynamic_cast, que convierte de forma segura un puntero (o referencia) a un tipo base en un puntero (o referencia) a un tipo derivado.

por ejemplo 1:

#include <iostream>
using namespace std;

class A
{
public:
    virtual void f(){cout << "A::f()" << endl;}
};

class B : public A
{
public:
    void f(){cout << "B::f()" << endl;}
};

int main()
{
    A a;
    B b;
    a.f();        // A::f()
    b.f();        // B::f()

    A *pA = &a;   
    B *pB = &b;   
    pA->f();      // A::f()
    pB->f();      // B::f()

    pA = &b;
    // pB = &a;      // not allowed
    pB = dynamic_cast<B*>(&a); // allowed but it returns NULL

    return 0;
}

Para más información haga clic aquí.

por ejemplo 2:

#include <iostream>

using namespace std;

class A {
public:
    virtual void print()const {cout << " A\n";}
};

class B {
public:
    virtual void print()const {cout << " B\n";}
};

class C: public A, public B {
public:
    void print()const {cout << " C\n";}
};


int main()
{

    A* a = new A;
    B* b = new B;
    C* c = new C;

    a -> print(); b -> print(); c -> print();
    b = dynamic_cast< B*>(a);  //fails
    if (b)  
       b -> print();  
    else 
       cout << "no B\n";
    a = c;
    a -> print(); //C prints
    b = dynamic_cast< B*>(a);  //succeeds
    if (b)
       b -> print();  
    else 
       cout << "no B\n";
}
Yogeesh HT
fuente