web-dev-qa-db-fra.com

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<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 définition dit: 

Le mot clé dynamic_cast jette une donnée à partir d'un pointeur ou d'une référence taper à un autre en effectuant une vérification de l'exécution pour s'assurer de la validité de la conversion

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

131
Vijay

Voici un récapitulatif sur static_cast<> et dynamic_cast<> spécifiquement en ce qui concerne les pointeurs. Ceci est juste un aperçu de 101 niveaux, il ne couvre pas toutes les subtilités.

static_cast <Type *> (ptr)

Cela prend le pointeur dans ptr et tente de le convertir en toute sécurité en un pointeur de type Type*. Ce casting est fait à la compilation. Il n'effectuera la conversion que si les types de types sont liés. Si les types ne sont pas liés, vous obtiendrez une erreur du compilateur. Par exemple:

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 <Type *> (ptr)

Ceci essaie à nouveau de prendre le pointeur dans ptr et de le transtyper en un pointeur de type Type*. Mais cette distribution est exécutée au moment de l'exécution, pas à la compilation. S'agissant d'une conversion au moment de l'exécution, elle est particulièrement utile lorsqu'elle est combinée à des classes polymorphes. En fait, dans les cas de certian, les classes doivent sont polymorphes pour que la distribution soit légale.

Les conversions 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). Il est assez simple de voir comment les conversions D2B fonctionneraient au moment de l'exécution. ptr a été dérivé de Type ou non. Dans le cas de D2B dynamic_cast <>, les règles sont simples. Vous pouvez essayer de transtyper n'importe quoi, et si ptr a été dérivé de Type, vous obtiendrez un pointeur Type* à partir de dynamic_cast. Sinon, vous obtiendrez un pointeur NULL. 

Mais les conversions en B2D sont un peu plus compliquées. Considérons le code suivant:

#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() ne peut pas dire quel type d'objet CreateRandom() va revenir, donc la conversion Bar* bar = (Bar*)base; de style C n'est décidément pas sécurisée. Comment pourriez-vous résoudre ce problème? Une solution serait d'ajouter une fonction telle que bool AreYouABar() const = 0; à la classe de base et de renvoyer true de Bar et false 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<Bar*>(base);
        Foo* foo = dynamic_cast<Foo*>(base);
        if( bar )
            bar->BarIt();
        if( foo )
            foo->FooIt();
    }
  return 0;

}

Les transtypages s’exécutent au moment de l’exécution et interrogent l’objet (inutile de s’inquiéter pour le moment), en lui demandant s’il s’agit du type recherché. Si c'est le cas, dynamic_cast<Type*> renvoie un pointeur; sinon, il retourne NULL.

Pour que ce transtypage base-à-dérivé fonctionne avec dynamic_cast<>, Base, Foo et Bar doivent correspondre à ce que la norme appelle 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 dérivée ne sera pas compilée. Exemple:

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


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

    return 0;
}

L'ajout d'une fonction virtuelle à la base, telle qu'un dtor virtuel, rendra 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<Der*>(base); // OK

    return 0;
}
243
John Dibling

Sauf si vous implémentez votre propre RTTI roulé à la main (et contournez celui du système), il est impossible d'implémenter dynamic_cast directement dans le code de niveau utilisateur C++. dynamic_cast est très étroitement lié au système RTTI de l’implémentation C++.

Toutefois, pour vous aider à mieux comprendre RTTI (et donc dynamic_cast), vous devez lire l’en-tête <typeinfo> et l’opérateur typeid. Cela retourne les informations de type correspondant à l'objet que vous avez sous la main, et vous pouvez demander diverses informations (limitées) à partir de ces objets d'informations de type.

20
Chris Jester-Young

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

Étant donné une classe de base pour laquelle il existe une classe dérivée Dérivée, dynamic_cast convertira un pointeur de base en un pointeur Dérivé si et seulement si l'objet indiqué est en réalité un objet Dérivé.

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
}

Dans l'exemple, l'appel à test lie différents objets à une référence à Base. En interne, la référence est downcasted à une référence à Derived de manière typée: la downcast ne réussira que dans les cas où l'objet référencé est bien une instance de Derived.

Un dynamic_cast effectue une vérification de type à l'aide de RTTI . Si cela échoue, il vous lève une exception (si vous lui avez donné une référence) ou NULL si vous lui avez donné un pointeur.

3
f4.

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

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

Non, pas facilement. Le compilateur attribue une identité unique à chaque classe. Cette information est référencée par chaque instance d'objet. C'est ce qui est inspecté lors de l'exécution pour déterminer si une conversion dynamique est légale. Vous pouvez créer une classe de base standard avec ces informations et des opérateurs pour effectuer l'inspection à 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 et toutes les instances de ces classes pourraient être exécutées à l'exécution à l'aide de vos opérations.

modifier

Voici une implémentation qui illustre 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 <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;
}
1
David Gladfelter

Il n'y a pas de classes en 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 "dynamique".

1
a1ex07

dynamic_cast utilise RTTI. Il peut ralentir votre application, vous pouvez utiliser la modification du modèle de conception du visiteur pour obtenir un downcasting sans RTTI http://arturx64.github.io/programming-world/2016/02/02/lazy-visitor.html

1
arturx64

Premièrement, pour décrire la conversion dynamique en termes C, nous devons représenter les classes en C . Les classes avec des fonctions virtuelles utilisent un "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])();

Ensuite, un casting dynamique est quelque chose comme:

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