dynamic_cast et static_cast en C ++

155

Je suis assez confus avec le dynamic_castmot - clé 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 dynamic_castmot-clé convertit une donnée 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 conversion

Pouvons-nous écrire un équivalent de dynamic_castC ++ en C pour que je puisse mieux comprendre les choses?

Vijay
la source
1
Si vous voulez avoir une bonne idée de la façon dont dynamic_cast<>fonctionne dans les coulisses (ou combien de C ++ fonctionne), un bon livre (qui est aussi assez facile à lire pour quelque chose d'aussi technique) est "Inside the C ++ Object Model" de Lippman. Les livres "Design and Evolution of C ++" et "The C ++ Programming Language" de Stroustrup sont également de bonnes ressources, mais le livre de Lippman est dédié à la manière dont C ++ fonctionne "en coulisses".
Michael Burr
Que signifie le commentaire dans la ligne B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to bou quoi?
LRDPRDX
@BogdanSikach De quelle question s'agit-il? Cela signifie simplement que l'ap est maintenant un type de classe B

Réponses:

282

Voici un aperçu static_cast<>et en dynamic_cast<>particulier 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 ptret tente de le convertir en toute sécurité en un pointeur de type Type*. Cette distribution est effectuée au moment de la compilation. Il n'effectuera le cast que si les types de type 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)

Cela tente à nouveau de prendre le pointeur ptret de le convertir en toute sécurité en un pointeur de type Type*. Mais ce cast est exécuté au moment de l'exécution, pas au moment de la compilation. Comme il s'agit d'un cast au moment de l'exécution, il est particulièrement utile lorsqu'il est combiné avec des classes polymorphes. En fait, dans les cas certiens, les classes doivent être polymorphes pour que la distribution soit légale.

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

Mais les lancers B2D sont un peu plus compliqués. Considérez 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()retournera, donc le cast de style C Bar* bar = (Bar*)base;n'est décidément pas de type sécurisé. Comment pouvez-vous résoudre ce problème? Une façon serait d'ajouter une fonction comme bool AreYouABar() const = 0;à la classe de base et de retourner truede Baret falsede 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 casts s'exécutent au moment de l'exécution et fonctionnent en interrogeant l'objet (pas besoin de s'inquiéter de savoir comment pour le moment), en lui demandant s'il est le type que nous recherchons. Si c'est le cas, dynamic_cast<Type*>renvoie un pointeur; sinon, il renvoie NULL.

Pour que cette conversion de base en dérivé fonctionne avec dynamic_cast<>, Base, Foo et Bar doivent être ce que le Standard appelle des types polymorphes . Pour être de type polymorphe, votre classe doit avoir au moins une virtualfonction. Si vos classes ne sont pas des types polymorphes, l'utilisation de base à dérivée de dynamic_castne 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, comme 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;
}
John Dibling
la source
9
Pourquoi le compilateur s'en plaint-il en premier lieu? et n'est-ce pas quand on ne fournit qu'un décodeur virtuel pour la base seulement?
Rika
5
Il convient de noter que si vous le faites Base* base = new Base;, le dynamic_cast<Foo*>(base)sera NULL.
Yay295
2
@ Coderx7 dynamic_cast nécessite des informations de type à l'exécution (RTTI) qui ne sont disponibles que pour les classes polymorphes, c'est-à-dire les classes avec au moins une méthode virtuelle.
Elvorfirilmathredia
@ Yay295 Pourquoi le dynamic_cast<Foo*>(base)est nul en cas de a Base* base = new Base;?
MuneshSingh
3
@munesh Parce que ce basen'est pas un Foo. Un Basepointeur peut pointer vers a Foo, mais c'est toujours un Foo, donc une distribution dynamique fonctionnera. Si vous le faites Base* base = new Base, baseest a Base, pas a Foo, vous ne pouvez donc pas le convertir dynamiquement en a Foo.
Yay295
20

À moins que vous n'implémentiez votre propre RTTI roulé à la main (et en contournant celui du système), il n'est pas possible de l'implémenter dynamic_castdirectement dans le code de niveau utilisateur C ++. dynamic_castest très lié au système RTTI de l'implémentation C ++.

Mais, pour vous aider à comprendre RTTI (et donc dynamic_cast) plus, vous devriez lire l'en- <typeinfo>tête et l' typeidopérateur. Cela renvoie les informations de type correspondant à l'objet que vous avez sous la main, et vous pouvez demander diverses choses (limitées) à partir de ces objets d'informations de type.

Chris Jester-Young
la source
Je voudrais vous indiquer Wikipedia, mais ses articles sur RTTI et dynamic_castsont très maigres. :-P Jouez simplement avec vous-même jusqu'à ce que vous compreniez. :-)
Chris Jester-Young
10

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

Étant donné une classe Base dont il existe une classe dérivée Derived, dynamic_castconvertira un pointeur Base en pointeur Derived si et seulement si l'objet réel pointé est en fait un objet Derived.

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 à testlie différents objets à une référence à Base. En interne, la référence est descendue vers une référence à Derivedde manière sécurisée: la conversion descendante ne réussira que dans les cas où l'objet référencé est effectivement une instance de Derived.

David Rodríguez - Dribeas
la source
2
Je pense qu'il est préférable de clarifier que les exemples partagés ci-dessus fonctionneront sur la base d'hypothèses si les classes sont polymorphes uniquement, c'est-à-dire qu'au moins la classe de base a au moins une méthode virtuelle.
irsis
1
Cela échouera car les classes ne sont pas des types polymorphes.
username_4567
4

Ce qui suit n'est pas vraiment proche de ce que vous obtenez de C ++ dynamic_casten termes de vérification de type, mais peut-être que cela vous aidera à comprendre un peu mieux 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;
}
Manuel
la source
3

A dynamic_casteffectue une vérification de type à l'aide de 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.

f4.
la source
2

Premièrement, pour décrire la conversion dynamique en termes C, nous devons représenter des 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 );
David Rayna
la source
1
La question initiale était "Pouvons-nous écrire un équivalent de dynamic_cast de C ++ en C".
David Rayna
1

Il n'y a pas de classes en C, il est donc impossible d'écrire dynamic_cast dans ce langage. Les structures C n'ont pas de méthodes (par conséquent, elles n'ont pas de méthodes virtuelles), donc il n'y a rien de "dynamique" dedans.

a1ex07
la source
1

Non, pas facilement. Le compilateur attribue une identité unique à chaque classe, ces informations sont référencées par chaque instance d'objet, et c'est ce qui est inspecté au moment de l'exécution pour déterminer si une distribution dynamique est légale. Vous pouvez créer une classe de base standard avec ces informations et opérateurs pour effectuer l'inspection d'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 castées à l'exécution via vos opérations.

Éditer

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;
}
David Gladfelter
la source
0

static_cast< Type* >(ptr)

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

dynamic_cast< Type* >(ptr)

dynamic_cast en C ++ peut être utilisé pour effectuer une conversion descendante sécurisée de type . dynamic_cast est le polymorphisme à l'exécution. L'opérateur dynamic_cast, qui convertit en toute sécurité d'un pointeur (ou référence) à un type de base en un pointeur (ou une référence) à un type dérivé.

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

Pour plus d'informations cliquez ici

par exemple 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
la source