dynamic_cast e static_cast in C ++


155

Sono abbastanza confuso con la dynamic_castparola chiave in 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 definizione dice:

La dynamic_castparola chiave esegue il cast di un dato da un puntatore o tipo di riferimento a un altro, eseguendo un controllo di runtime per garantire la validità del cast

Possiamo scrivere un equivalente di dynamic_castC ++ in C per capire meglio le cose?


1
Se vuoi avere una buona idea di come dynamic_cast<>funziona dietro le quinte (o di quanto C ++ funziona), un buon libro (che è anche abbastanza facile da leggere per qualcosa di così tecnico) è "Inside the C ++ Object Model" di Lippman. Anche i libri di Stroustrup "Design and Evolution of C ++" e "The C ++ Programming Language" sono buone risorse, ma il libro di Lippman è dedicato al modo in cui il C ++ funziona "dietro le quinte".
Michael Burr,

Cosa significa il commento nella riga B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to bo cosa?
LRDPRDX

@BogdanSikach Che domanda è? Significa semplicemente che l'

Risposte:


282

Ecco un riassunto su static_cast<>e in dynamic_cast<>particolare per quanto riguarda i puntatori. Questa è solo una carrellata di 101 livelli, non copre tutte le complessità.

static_cast <Tipo *> (ptr)

In questo modo il puntatore viene ptrinserito e si tenta di eseguirne il cast sicuro in un puntatore di tipo Type*. Questo cast viene eseguito in fase di compilazione. Eseguirà il cast solo se i tipi di tipo sono correlati. Se i tipi non sono correlati, verrà visualizzato un errore del compilatore. Per esempio:

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)

Questo tenta di nuovo di ptrinserire il puntatore e di inserirlo in modo sicuro in un puntatore di tipo Type*. Ma questo cast viene eseguito in fase di esecuzione, non in fase di compilazione. Poiché si tratta di un cast di runtime, è utile soprattutto se combinato con classi polimorfiche. In effetti, in casi certiani le classi devono essere polimorfiche affinché il cast sia legale.

I cast possono andare in una delle due direzioni: dalla base alla derivata (B2D) o dalla derivata alla base (D2B). È abbastanza semplice vedere come funzionano i cast di D2B in fase di esecuzione. O è ptrstato derivato Typeo non lo era. Nel caso di D2B dynamic_cast <> s, le regole sono semplici. Puoi provare a trasmettere qualsiasi cosa a qualsiasi altra cosa, e se ptrin realtà è derivato da Type, otterrai un Type*puntatore da dynamic_cast. Altrimenti, otterrai un puntatore NULL.

Ma i lanci B2D sono un po 'più complicati. Considera il seguente codice:

#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()non so quale tipo di oggetto CreateRandom()restituirà, quindi il cast in stile C Bar* bar = (Bar*)base;non è decisamente sicuro. Come hai potuto risolvere questo? Un modo sarebbe quello di aggiungere una funzione come bool AreYouABar() const = 0;alla classe base e tornare trueda Bare falseversoFoo . Ma c'è un altro modo: usare 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;

}

I cast vengono eseguiti in fase di esecuzione e funzionano eseguendo una query sull'oggetto (non è necessario preoccuparsi di come per ora), chiedendogli se è il tipo che stiamo cercando. In tal caso, dynamic_cast<Type*>restituisce un puntatore; altrimenti restituisce NULL.

Affinché questa fusione base-derivata funzioni usando dynamic_cast<>, Base, Foo e Bar devono essere quelli che lo Standard chiama tipi polimorfici . Per essere un tipo polimorfico, la tua classe deve avere almeno una virtualfunzione. Se le tue classi non sono tipi polimorfici, l'uso base-derivato dynamic_castnon verrà compilato. Esempio:

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'aggiunta di una funzione virtuale alla base, come un dtor virtuale, renderà i tipi polimorfici sia Base che 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;
}

9
Perché il compilatore si lamenta al primo posto? e quando forniamo solo un dctor virtuale solo per base?
Rika,

5
Va notato che se lo fai Base* base = new Base;, dynamic_cast<Foo*>(base)lo sarà NULL.
Yay 295

2
@ Coderx7 dynamic_cast necessita di informazioni sul tipo di runtime (RTTI) che sono disponibili solo per le classi polimorfiche, vale a dire le classi con almeno un metodo virtuale.
Elvorfirilmathredia,

@ Yay295 Perché dynamic_cast<Foo*>(base)è null in caso di a Base* base = new Base;?
MuneshSingh,

3
@munesh Perché basenon è un Foo. Un Basepuntatore può puntare a Foo, ma è comunque a Foo, quindi un cast dinamico funzionerà. Se lo fai Base* base = new Base, baseè un Base, non un Foo, quindi non puoi lanciarlo dinamicamente in un Foo.
Yay 295

20

A meno che non si stia implementando il proprio RTTI a rotazione manuale (e bypassando quello di sistema), non è possibile implementare dynamic_castdirettamente nel codice a livello di utente C ++.dynamic_castè molto legato al sistema RTTI dell'implementazione C ++.

Ma, per aiutarti a capire meglio RTTI (e quindi dynamic_cast), dovresti leggere sull'intestazione <typeinfo>e l' typeidoperatore. Ciò restituisce le informazioni sul tipo corrispondenti all'oggetto che hai a portata di mano, e puoi richiedere varie (limitate) cose da questi oggetti informazioni sul tipo.


Vorrei indicarti Wikipedia, ma i suoi articoli su RTTI e dynamic_castsono molto succinti. :-P Basta giocarci fino a quando non ne hai capito. :-)
Chris Jester-Young,

10

Più che il codice in C, penso che una definizione inglese potrebbe essere sufficiente:

Data una classe Base di cui è derivata una classe derivata, dynamic_castconvertirà un puntatore Base in un puntatore Derivato se e solo se l'oggetto effettivo puntato è in realtà un oggetto Derivato.

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
}

Nell'esempio, la chiamata per testassociare oggetti diversi a un riferimento a Base. Internamente il riferimento viene downcastato a un riferimento Derivedin un modo typesafe: il downcast avrà successo solo per quei casi in cui l'oggetto referenziato è effettivamente un'istanza di Derived.


2
Penso che sia meglio chiarire che gli esempi condivisi sopra funzioneranno in base a ipotesi se le classi sono solo polimorfiche, cioè almeno la classe Base ha almeno un metodo virtuale.
irsis,

1
Ciò fallirà perché le classi non sono tipi polimorfici.
username_4567

4

Quanto segue non è molto vicino a quello che ottieni dal C ++ dynamic_castin termini di controllo del tipo, ma forse ti aiuterà a capire un po 'meglio il suo scopo:

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

A dynamic_castesegue un controllo del tipo mediante RTTI . Se fallisce, ti genererà un'eccezione (se gli hai dato un riferimento) o NULL se gli hai dato un puntatore.


2

Innanzitutto, per descrivere il cast dinamico in termini di C, dobbiamo rappresentare le classi in C. Le classi con funzioni virtuali usano un "VTABLE" di puntatori alle funzioni virtuali. I commenti sono in C ++. Sentiti libero di riformattare e correggere errori di compilazione ...

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

Quindi un cast dinamico è qualcosa del tipo:

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

1
La domanda iniziale era "Possiamo scrivere un equivalente di dynamic_cast di C ++ in C".
David Rayna,

1

Non ci sono classi in C, quindi è impossibile scrivere dynamic_cast in quella lingua. Le strutture C non hanno metodi (di conseguenza, non hanno metodi virtuali), quindi non c'è nulla di "dinamico" in esso.


1

No, non facilmente. Il compilatore assegna un'identità unica a ogni classe, che le informazioni fanno riferimento a ogni istanza di oggetto, ed è ciò che viene ispezionato in fase di esecuzione per determinare se un cast dinamico è legale. È possibile creare una classe base standard con queste informazioni e operatori per eseguire l'ispezione di runtime su quella classe base, quindi qualsiasi classe derivata informerebbe la classe base del suo posto nella gerarchia di classi e qualsiasi istanza di tali classi sarà calcolabile tramite runtime tramite le tue operazioni.

modificare

Ecco un'implementazione che dimostra una tecnica. Non sto affermando che il compilatore usi qualcosa del genere, ma penso che dimostri i concetti:

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


0

static_cast< Type* >(ptr)

static_cast in C ++ può essere utilizzato in scenari in cui tutti i tipi di casting possono essere verificati in fase di compilazione .

dynamic_cast< Type* >(ptr)

dynamic_cast in C ++ può essere utilizzato per eseguire il casting sicuro dei tipi . dynamic_cast è il polimorfismo del tempo di esecuzione. L'operatore dynamic_cast, che converte in modo sicuro da un puntatore (o riferimento) a un tipo di base in un puntatore (o riferimento) in un tipo derivato.

ad es. 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;
}

Per maggiori informazioni clicca qui

ad es. 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";
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.