dynamic_cast y static_cast en C ++

Estoy bastante confundido con la palabra clave dynamic_cast 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 (&a); // NULL, because 'a' is not a 'B' B* b2 = dynamic_cast (ap); // 'b' C* c = dynamic_cast (ap); // NULL. A& ar = dynamic_cast (*ap); // Ok. B& br = dynamic_cast (*ap); // Ok. C& cr = dynamic_cast (*ap); // std::bad_cast } 

la definición dice:

La palabra clave dynamic_cast arroja un dato de un puntero o tipo de referencia a otro, realizando una verificación en tiempo de ejecución para asegurar la validez del elenco

¿Podemos escribir un equivalente de dynamic_cast de C ++ en C para que yo pueda entender mejor las cosas?

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

static_cast (ptr)

Esto toma el puntero en ptr e intenta convertirlo de manera segura en un puntero de tipo Type* . Este elenco se realiza en tiempo de comstackción. Solo realizará el lanzamiento si los tipos de tipo están relacionados. Si los tipos no están relacionados, obtendrá un error de comstackción. Por ejemplo:

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

dynamic_cast (ptr)

De nuevo, intenta tomar el puntero en ptr y ptr forma segura en un puntero de tipo Type* . Pero este lanzamiento se ejecuta en tiempo de ejecución, no en tiempo de comstackción. Como se trata de un lanzamiento en tiempo de ejecución, es útil especialmente cuando se combina con clases polimórficas. De hecho, en ciertos casos las clases deben ser polimórficas para que el elenco 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 funcionan los moldes D2B en tiempo de ejecución. O bien ptr se derivó de Type o no. En el caso de D2B dynamic_cast <> s, las reglas son simples. Puede tratar de convertir cualquier cosa a cualquier otra cosa, y si ptr de hecho se deriva de Type , obtendrá un puntero Type* de dynamic_cast . De lo contrario, obtendrás un puntero NULL.

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

 #include  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 puede decir qué tipo de objeto CreateRandom() , por lo que la Bar* bar = (Bar*)base; colada estilo C Bar* bar = (Bar*)base; es decididamente no seguro de tipo. ¿Cómo puedes arreglar esto? Una forma sería agregar una función como bool AreYouABar() const = 0; a la clase base y devuelve true de Bar y false de 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(base); Foo* foo = dynamic_cast(base); if( bar ) bar->BarIt(); if( foo ) foo->FooIt(); } return 0; } 

Los moldes se ejecutan en tiempo de ejecución y funcionan al consultar el objeto (no hay por qué preocuparse por cómo) y le preguntan si es del tipo que estamos buscando. Si lo es, dynamic_cast devuelve un puntero; de lo contrario, devuelve NULL.

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

 class Base {}; class Der : public Base {}; int main() { Base* base = new Der; Der* der = dynamic_cast(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(base); // OK return 0; } 

A menos que esté implementando su propio RTTI enrollado a mano (y eludiendo el del sistema uno), no es posible implementar el dynamic_cast directamente en el código de nivel de usuario de C ++. dynamic_cast está muy ligado al sistema RTTI de la implementación de C ++.

Sin embargo, para ayudarlo a comprender RTTI (y, por lo tanto, dynamic_cast ), debe leer en el y en el operador typeid . 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.

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_cast convertirá un puntero Base en puntero Derivado si y solo si el objeto real apuntado es en realidad 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(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 test vincula diferentes objetos a una referencia a Base . Internamente, la referencia se descuenta a una referencia a Derived de una manera segura: el downcast solo tendrá éxito para aquellos casos en los que el objeto al que se hace referencia sea de hecho una instancia de Derived .

Un dynamic_cast realiza una verificación de tipo utilizando RTTI . Si falla le lanzará una excepción (si le dio una referencia) o NULL si le dio un puntero.

Lo siguiente no está muy cerca de lo que obtienes del dynamic_cast de C ++ en términos de verificación de tipos, pero tal vez te ayude a entender su propósito un poco mejor:

 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; } 

No hay clases en C, por lo que es imposible escribir dynamic_cast en ese idioma. Las estructuras C no tienen métodos (como resultado, no tienen métodos virtuales), por lo que no hay nada “dynamic” en él.

No, no es fácil. El comstackdor asigna una identidad única a cada clase, esa información es referenciada por cada instancia de objeto, y eso es lo que se inspecciona durante el tiempo de ejecución para determinar si un lanzamiento dynamic es legal. Podrías crear una clase base estándar con esta información y operadores para hacer la inspección en 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 tus operaciones

editar

Aquí hay una implementación que demuestra una técnica. No pretendo que el comstackdor 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  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(pChild); SafeCastableBase *pBaseToChild = SafeCastableBase::SafeCast(pChild); ChildCastable *pNullDownCast = SafeCastableBase::SafeCast(pBase); SafeCastableBase *pBaseToPeerChild = SafeCastableBase::SafeCast(pPeerChild); ChildCastable *pNullCrossCast = SafeCastableBase::SafeCast(pPeerChild); return 0; } 

dynamic_cast usa RTTI. Puede ralentizar su aplicación, puede usar la modificación del patrón de diseño del visitante para lograr downcasting sin RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

static_cast< Type* >(ptr)

static_cast en C ++ se puede usar en escenarios en los que todo tipo de conversión se puede verificar en tiempo de comstackción .

dynamic_cast< Type* >(ptr)

dynamic_cast en C ++ se puede usar para realizar un tipo de transmisión por caída segura . dynamic_cast es el polymorphism de tiempo de ejecución. El operador dynamic_cast, que convierte de manera segura de un puntero (o referencia) a un tipo base a un puntero (o referencia) a un tipo derivado.

por ej. 1:

 #include  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; af(); // A::f() bf(); // 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(&a); // allowed but it returns NULL return 0; } 

Para más información haga clic aquí

por ej. 2:

 #include  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"; } 

Primero, para describir el reparto dynamic en términos de C, tenemos que representar 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 reformatear y corregir errores de comstackció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 lanzamiento dynamic es algo así como:

 // A * ptr = new B(); A * ptr = (A*) newB(); // B * aB = dynamic_cast(ptr); B * aB = ( ptr->vtable == Bvtable ? (B*) aB : (B*) 0 );