dynamic_cast en static_cast in C++

Ik ben nogal in de war met het trefwoord dynamic_cast 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
}

de definitie zegt:

Het sleutelwoord dynamic_cast werpt een datum vanaf één pointer of referentie
typ naar een ander, voer een runtime-controle uit om de geldigheid van de cast te garanderen

Kunnen we een equivalent van dynamic_cast van C++ in C schrijven zodat ik dingen beter kan begrijpen?


Antwoord 1, autoriteit 100%

Hier is een overzicht van static_cast<> en dynamic_cast<>, specifiek omdat ze betrekking hebben op pointers. Dit is slechts een overzicht van 101 niveaus, het behandelt niet alle fijne kneepjes.

static_cast< Typ* >(ptr)

Dit neemt de aanwijzer in ptr en probeert deze veilig naar een aanwijzer van het type Type* te casten. Deze cast wordt gedaan tijdens het compileren. Het zal de cast alleen uitvoeren als de typen gerelateerd zijn. Als de typen niet gerelateerd zijn, krijgt u een compilerfout. Bijvoorbeeld:

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

Dit probeert opnieuw de aanwijzer in ptr te nemen en deze veilig naar een aanwijzer van het type Type* te casten. Maar deze cast wordt uitgevoerd tijdens runtime, niet tijdens compileren. Omdat dit een runtime-cast is, is het vooral handig in combinatie met polymorfe klassen. In bepaalde gevallen moeten de klassen moeten polymorf zijn om de cast legaal te laten zijn.

Casts kunnen in twee richtingen gaan: van basis naar afgeleid (B2D) of van afgeleid naar basis (D2B). Het is eenvoudig genoeg om te zien hoe D2B-casts tijdens runtime zouden werken. Ofwel ptr is afgeleid van Type of niet. In het geval van D2B dynamic_cast<>s zijn de regels eenvoudig. Je kunt proberen alles naar iets anders te casten, en als ptr in feite is afgeleid van Type, krijg je een Type*-aanwijzer terug van dynamic_cast. Anders krijg je een NULL-aanwijzer.

Maar B2D-casts zijn iets ingewikkelder. Overweeg de volgende code:

#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() kan niet zeggen wat voor soort object CreateRandom() zal terugkeren, dus de cast in C-stijl Bar* bar = (Bar*)base; is beslist niet typeveilig. Hoe zou je dit kunnen oplossen? Een manier zou zijn om een ​​functie zoals bool AreYouABar() const = 0; toe te voegen aan de basisklasse en true terug te geven van Bar en false van Foo. Maar er is een andere manier: gebruik 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;
}

De casts worden uitgevoerd tijdens runtime en werken door het object te bevragen (u hoeft zich voorlopig geen zorgen te maken over hoe), en vragen of dit het type is waarnaar we op zoek zijn. Als dit het geval is, retourneert dynamic_cast<Type*> een pointer; anders geeft het NULL terug.

Om ervoor te zorgen dat deze cast-naar-afgeleide casting werkt met dynamic_cast<>, moeten Base, Foo en Bar zijn wat de standaard polymorfe typen noemt. Om een ​​polymorf type te zijn, moet je klasse minstens één virtual functie hebben. Als uw klassen geen polymorfe typen zijn, wordt het van basis naar afgeleide gebruik van dynamic_cast niet gecompileerd. Voorbeeld:

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

Door een virtuele functie aan base toe te voegen, zoals een virtuele dtor, worden zowel Base als Der polymorfe typen:

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

Antwoord 2, autoriteit 6%

Tenzij u uw eigen handgerolde RTTI implementeert (en de systeemversie omzeilt), is het niet mogelijk om dynamic_cast rechtstreeks in C++-code op gebruikersniveau te implementeren. dynamic_cast is nauw verbonden met het RTTI-systeem van de C++-implementatie.

Maar om je te helpen RTTI (en dus dynamic_cast) beter te begrijpen, moet je de kop <typeinfo> en de typeid-operator. Dit geeft de type-info terug die overeenkomt met het object dat je bij de hand hebt, en je kunt verschillende (beperkte) dingen opvragen bij deze type-info-objecten.


Antwoord 3, autoriteit 4%

Meer dan code in C, ik denk dat een Engelse definitie voldoende zou kunnen zijn:

Gegeven een klasse Base waarvan er een afgeleide klasse Derived is, zal dynamic_cast een Base-pointer converteren naar een Derived pointer als en alleen als het werkelijke object waarnaar wordt verwezen in feite een afgeleid object is.

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
}

In het voorbeeld bindt de aanroep van test verschillende objecten aan een verwijzing naar Base. Intern is de verwijzing gedowncast naar een verwijzing naar Derived op een typeveilige manier: de downcast zal alleen slagen in die gevallen waarin het object waarnaar wordt verwezen inderdaad een instantie is van Derived.


Antwoord 4

Het volgende komt niet echt in de buurt van wat u krijgt van C++’s dynamic_cast in termen van typecontrole, maar misschien zal het u helpen het doel ervan een beetje beter te begrijpen:

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

Antwoord 5

Ten eerste, om dynamische cast in C-termen te beschrijven, moeten we klassen in C vertegenwoordigen.
Klassen met virtuele functies gebruiken een “VTABLE” van verwijzingen naar de virtuele functies.
Opmerkingen zijn C++. Voel je vrij om opnieuw te formatteren en compileerfouten op te lossen…

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

Dan is een dynamische cast zoiets als:

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

Antwoord 6

Een dynamic_cast voert een typecontrole uit met behulp van RTTI . Als het mislukt, krijg je een uitzondering (als je het een verwijzing hebt gegeven) of NULL als je het een aanwijzer hebt gegeven.


Antwoord 7

Er zijn geen klassen in C, dus het is onmogelijk om dynamic_cast in die taal te schrijven. C-structuren hebben geen methoden (als resultaat hebben ze geen virtuele methoden), dus er zit niets “dynamisch” in.


Antwoord 8

Nee, niet gemakkelijk. De compiler wijst een unieke identiteit toe aan elke klasse, naar die informatie wordt verwezen door elke objectinstantie, en dat is wat tijdens runtime wordt geïnspecteerd om te bepalen of een dynamische cast legaal is. Je zou een standaard basisklasse kunnen maken met deze informatie en operators om de runtime-inspectie op die basisklasse uit te voeren, dan zou elke afgeleide klasse de basisklasse informeren over zijn plaats in de klassenhiërarchie en alle instanties van die klassen zouden runtime-castable zijn via uw activiteiten.

bewerken

Hier is een implementatie die één techniek demonstreert. Ik beweer niet dat de compiler zoiets gebruikt, maar ik denk dat het de concepten demonstreert:

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

Antwoord 9

static_cast< Type* >(ptr)

static_cast in C++ kan worden gebruikt in scenario’s waarin alle typecasting kan worden geverifieerd tijdens het compileren.

dynamic_cast< Type* >(ptr)

dynamic_cast in C++ kan worden gebruikt om type safe downcasting uit te voeren. dynamic_cast is runtime-polymorfisme. De operator dynamic_cast, die veilig converteert van een aanwijzer (of verwijzing) naar een basistype naar een aanwijzer (of verwijzing) naar een afgeleid type.

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

Voor meer informatie klik hier

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

Antwoord 10

dynamic_cast gebruikt RTTI. Het kan uw applicatie vertragen, u kunt een wijziging van het bezoekersontwerppatroon gebruiken om downcasting te bereiken zonder RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes