dynamic_cast et static_cast en C ++

Je suis assez confus avec le mot-clé 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 définition dit:

Le mot clé dynamic_cast convertit un datum d’un pointeur ou d’un type de référence à un autre, en effectuant une vérification à l’exécution pour garantir la validité de la dissortingbution.

Peut-on écrire un équivalent de dynamic_cast de C ++ en C pour mieux comprendre les choses?

    Voici un aperçu des éléments static_cast<> et dynamic_cast<> particulier, car ils se rapportent à des pointeurs. Ceci est juste un aperçu de 101 niveaux, il ne couvre pas toutes les complexités.

    static_cast (ptr)

    Cela prend le pointeur dans ptr et essaie de le jeter en toute sécurité à un pointeur de type Type* . Ce casting est fait au moment de la compilation. Il effectuera uniquement le transtypage si les types de type sont liés. Si les types ne sont pas liés, vous obtiendrez une erreur de compilation. Par exemple:

     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 comstack return 0; } 

    dynamic_cast (ptr)

    Ceci tente à nouveau de prendre le pointeur dans ptr et de le lancer en toute sécurité vers un pointeur de type Type* . Mais cette dissortingbution est exécutée à l’exécution, pas à la compilation. Dans la mesure où il s’agit d’une dissortingbution d’exécution, elle est particulièrement utile lorsqu’elle est associée à des classes polymorphes. En fait, dans les cas certiens, les classes doivent être polymorphes pour que la dissortingbution soit légale.

    Les moulages peuvent aller dans l’une des deux directions: de la base à la dérivée (B2D) ou de la dérivée à la base (D2B). C’est assez simple pour voir comment D2B fonctionnerait au moment de l’exécution. Soit ptr était dérivé de Type ou il ne l’était pas. Dans le cas de D2B dynamic_cast <> s, les règles sont simples. Vous pouvez essayer de convertir quelque chose en autre chose, et si ptr était en fait dérivé de Type , vous obtiendrez un pointeur Type* à partir de dynamic_cast . Sinon, vous obtiendrez un pointeur NULL.

    Mais les castings B2D sont un peu plus compliqués. Considérez le code suivant:

     #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() ne peut pas dire quel type d’object CreateRandom() retournera, donc la Bar* bar = (Bar*)base; style C CreateRandom() Bar* bar = (Bar*)base; est décidément pas sûr de type. Comment pourriez-vous résoudre ce problème? Une façon serait d’append une fonction comme bool AreYouABar() const = 0; à la classe de base et retourner true partir de Bar et false partir de Foo . Mais il existe un autre moyen: utilisez 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; } 

    Les casts s’exécutent à l’exécution et fonctionnent en interrogeant l’object (pas besoin de s’inquiéter pour le moment), en lui demandant si c’est le type que nous recherchons. Si c’est le cas, dynamic_cast renvoie un pointeur; sinon il retourne NULL.

    Pour que ce casting de base à dérivé fonctionne en utilisant dynamic_cast<> , Base, Foo et Bar doivent être ce que le standard appelle des types polymorphes . Pour être un type polymorphe, votre classe doit avoir au moins une fonction virtual . Si vos classes ne sont pas des types polymorphes, l’utilisation de dynamic_cast de la base à la base ne sera pas compilée. Exemple:

     class Base {}; class Der : public Base {}; int main() { Base* base = new Der; Der* der = dynamic_cast(base); // ERROR - Won't comstack return 0; } 

    L’ajout d’une fonction virtuelle à la base, tel qu’un dtor virtuel, produira à la fois les types polymorphes Base et Der:

     class Base { public: virtual ~Base(){}; }; class Der : public Base {}; int main() { Base* base = new Der; Der* der = dynamic_cast(base); // OK return 0; } 

    À moins d’implémenter votre propre RTTI (et de contourner le système), il n’est pas possible d’implémenter dynamic_cast directement dans le code de niveau utilisateur C ++. dynamic_cast est très lié au système RTTI de l’implémentation C ++.

    Mais, pour vous aider à mieux comprendre RTTI (et donc dynamic_cast ), vous devriez lire l’en-tête et l’opérateur typeid . Cela retourne les informations de type correspondant à l’object que vous avez sous la main, et vous pouvez demander différentes choses (limitées) à partir de ces objects info de type.

    Plus que du code en C, je pense qu’une définition en anglais pourrait suffire:

    Etant donné une classe Base dont il existe une classe dérivée Derived, dynamic_cast convertira un pointeur Base en pointeur Derived si et seulement si l’object réel pointé est en fait un object Derived.

     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 } 

    Dans l’exemple, l’appel à test lie différents objects à une référence à Base . En interne, la référence est réduite à une référence à Derived de manière sécurisée: le downcast ne réussira que dans les cas où l’object référencé est bien une instance de Derived .

    Un dynamic_cast effectue une vérification de type en utilisant RTTI . S’il échoue, il vous lancera une exception (si vous lui avez donné une référence) ou NULL si vous lui avez donné un pointeur.

    Ce qui suit n’est pas vraiment proche de ce que vous obtenez de la fonction dynamic_cast de C ++ en termes de vérification de type, mais cela vous aidera peut-être à mieux comprendre son objective:

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

    Il n’y a pas de classes dans C, il est donc impossible d’écrire dynamic_cast dans cette langue. Les structures C n’ont pas de méthodes (par conséquent, elles n’ont pas de méthodes virtuelles), il n’y a donc rien de “dynamic”.

    Non, pas facilement Le compilateur atsortingbue une identité unique à chaque classe, cette information est référencée par chaque instance d’object, et c’est ce qui est inspecté à l’exécution pour déterminer si une dissortingbution dynamic est légale. Vous pourriez créer une classe de base standard avec ces informations et les opérateurs pour effectuer l’inspection au moment de l’exécution sur cette classe de base, puis toute classe dérivée informerait la classe de base de sa place dans la hiérarchie des classes. vos opérations.

    modifier

    Voici une implémentation qui démontre une technique. Je ne prétends pas que le compilateur utilise quelque chose comme ça, mais je pense que cela démontre les concepts:

     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 utilise RTTI. Il peut ralentir votre application, vous pouvez utiliser la modification du modèle de conception des visiteurs pour obtenir un downcasting sans RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

    static_cast< Type* >(ptr)

    static_cast en C ++ peut être utilisé dans des scénarios où tout type de transtypage peut être vérifié au moment de la compilation .

    dynamic_cast< Type* >(ptr)

    dynamic_cast en C ++ peut être utilisé pour effectuer une conversion de type down down . dynamic_cast est un polymorphism d’exécution. L’opérateur dynamic_cast, qui convertit en toute sécurité d’un pointeur (ou référence) en un type de base en un pointeur (ou référence) vers un type dérivé.

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

    Pour plus d’informations cliquez ici

    par exemple 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"; } 

    Premièrement, pour décrire la dissortingbution dynamic en termes C, nous devons représenter des classes en C. Les classes avec des fonctions virtuelles utilisent une “VTABLE” de pointeurs vers les fonctions virtuelles. Les commentaires sont en C ++. N’hésitez pas à reformater et corriger les erreurs de compilation …

     // 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])(); 

    Alors une dissortingbution dynamic est quelque chose comme:

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