dynamic_cast und static_cast in C ++

155

Ich bin ziemlich verwirrt mit dem dynamic_castSchlüsselwort 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
}

Die Definition lautet:

Das dynamic_castSchlüsselwort wandelt ein Datum von einem Zeiger oder Referenztyp in einen anderen um und führt eine Laufzeitprüfung durch, um die Gültigkeit der Umwandlung sicherzustellen

Können wir ein Äquivalent von dynamic_castC ++ in C schreiben , damit ich die Dinge besser verstehen kann?

Vijay
quelle
1
Wenn Sie eine gute Vorstellung davon bekommen möchten, wie dynamic_cast<>hinter den Kulissen funktioniert (oder wie viel von C ++ funktioniert), ist Lippmans "Inside the C ++ Object Model" ein gutes Buch (das auch für etwas so Technisches ziemlich einfach zu lesen ist). Auch Stroustrups Bücher "Design and Evolution of C ++" und "The C ++ Programming Language" sind gute Ressourcen, aber Lippmans Buch widmet sich der Funktionsweise von C ++ "hinter den Kulissen".
Michael Burr
Was bedeutet der Kommentar in der Zeile B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to boder was?
LRDPRDX
@ BogdanSikach Welche Frage ist das? Es bedeutet einfach, dass die ap jetzt eine Art B-Klasse ist

Antworten:

282

Hier ist ein Überblick über static_cast<>und dynamic_cast<>speziell in Bezug auf Zeiger. Dies ist nur ein Überblick über 101 Ebenen, der nicht alle Feinheiten abdeckt.

static_cast <Typ *> (ptr)

Dadurch wird der Zeiger aufgenommen ptrund versucht, ihn sicher in einen Zeiger vom Typ umzuwandeln Type*. Diese Umwandlung erfolgt zur Kompilierungszeit. Die Umwandlung wird nur durchgeführt, wenn die Typtypen verwandt sind. Wenn die Typen nicht verwandt sind, wird ein Compilerfehler angezeigt. Beispielsweise:

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)

Dies versucht erneut, den Zeiger aufzunehmen ptrund sicher in einen Zeiger vom Typ umzuwandeln Type*. Diese Umwandlung wird jedoch zur Laufzeit und nicht zur Kompilierungszeit ausgeführt. Da es sich um eine Laufzeitumwandlung handelt, ist sie insbesondere in Kombination mit polymorphen Klassen nützlich. In der Tat, in certian Fällen die Klassen müssen polymorph sein , um für die Besetzung legal.

Casts können in eine von zwei Richtungen gehen: von Basis zu abgeleitet (B2D) oder von abgeleitet zu Basis (D2B). Es ist einfach genug zu sehen, wie D2B-Casts zur Laufzeit funktionieren würden. Entweder ptrwurde abgeleitet von Typeoder es war nicht. Bei D2B dynamic_cast <> s sind die Regeln einfach. Sie können versuchen, alles in etwas anderes umzuwandeln, und wenn ptres tatsächlich von abgeleitet wurde Type, erhalten Sie einen Type*Zeiger zurück von dynamic_cast. Andernfalls erhalten Sie einen NULL-Zeiger.

B2D-Casts sind jedoch etwas komplizierter. Betrachten Sie den folgenden 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()Ich kann nicht sagen, welche Art von Objekt zurückgegeben CreateRandom()wird, daher ist die Besetzung im C-Stil Bar* bar = (Bar*)base;definitiv nicht typsicher. Wie können Sie das beheben? Eine Möglichkeit wäre AreYouABar() const = 0;, der Basisklasse eine Funktion wie bool hinzuzufügen und truevon Barund falsenach zurückzukehren Foo. Aber es gibt noch einen anderen Weg: Verwenden Sie 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;

}

Die Casts werden zur Laufzeit ausgeführt und arbeiten, indem sie das Objekt abfragen (Sie müssen sich vorerst keine Gedanken darüber machen, wie) und fragen, ob es der Typ ist, nach dem wir suchen. Wenn dies der Fall ist, wird dynamic_cast<Type*>ein Zeiger zurückgegeben. Andernfalls wird NULL zurückgegeben.

Damit dieses Casting von Basis zu Basis funktioniert dynamic_cast<>, müssen Base, Foo und Bar das sein, was der Standard als polymorphe Typen bezeichnet . Um ein polymorpher Typ zu sein, muss Ihre Klasse mindestens eine virtualFunktion haben. Wenn Ihre Klassen keine polymorphen Typen sind, wird die Verwendung von Basis zu Ableitung von dynamic_castnicht kompiliert. Beispiel:

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


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

    return 0;
}

Durch Hinzufügen einer virtuellen Funktion zur Basis, z. B. eines virtuellen Dtors, werden sowohl die Basis- als auch die Der-Polymorph-Typen erstellt:

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
quelle
9
Warum beschwert sich der Compiler überhaupt darüber? und nicht, wenn wir nur einen virtuellen Dctor nur für die Basis bereitstellen?
Rika
5
Es sollte beachtet werden, dass, wenn Sie dies tun Base* base = new Base;, dynamic_cast<Foo*>(base)wird NULL.
Yay295
2
@ Coderx7 dynamic_cast benötigt RTTI (Run-Time Type Information), die nur für polymorphe Klassen verfügbar sind, dh Klassen mit mindestens einer virtuellen Methode.
Elvorfirilmathredia
@ Yay295 Warum dynamic_cast<Foo*>(base)ist das bei a null Base* base = new Base;?
MuneshSingh
3
@ Munesh Weil baseist nicht ein Foo. Ein BaseZeiger kann auf a zeigen Foo, aber es ist immer noch a Foo, sodass eine dynamische Besetzung funktioniert. Wenn Sie dies tun Base* base = new Base, baseist a Base, nicht a Foo, sodass Sie es nicht dynamisch in a umwandeln können Foo.
Yay295
20

Es ist nicht möglich, dynamic_castdirekt in C ++ - Code auf Benutzerebene zu implementieren, es sei denn, Sie implementieren Ihre eigene handgerollte RTTI (und umgehen die System-RTTI) . dynamic_castist stark mit dem RTTI-System der C ++ - Implementierung verbunden.

Um Ihnen jedoch zu helfen, RTTI (und damit dynamic_cast) besser zu verstehen , sollten Sie den <typeinfo>Header und den typeidOperator nachlesen . Dies gibt die Typinformationen zurück, die dem Objekt entsprechen, das Sie zur Hand haben, und Sie können verschiedene (begrenzte) Dinge von diesen Typinfoobjekten abfragen.

Chris Jester-Young
quelle
Ich würde Sie auf Wikipedia verweisen, aber seine Artikel auf RTTI und dynamic_castsind sehr knapp. :-P Spielen Sie einfach selbst damit, bis Sie den Dreh raus haben. :-)
Chris Jester-Young
10

Mehr als Code in C denke ich, dass eine englische Definition ausreichen könnte:

Bei einer Klassenbasis, von der eine abgeleitete Klasse abgeleitet ist, dynamic_castwird ein Basiszeiger genau dann in einen abgeleiteten Zeiger konvertiert, wenn das tatsächliche Objekt, auf das gezeigt wird, tatsächlich ein abgeleitetes Objekt ist.

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
}

Im Beispiel testbindet der Aufruf zum Binden verschiedener Objekte an einen Verweis auf Base. Intern wird die Referenz downcasted einen Verweis auf Derivedeine typsichere Art und Weise: die niedergeschlagenen wird nur gelingen , für jene Fälle , in denen das referenzierte Objekt ist in der Tat eine Instanz Derived.

David Rodríguez - Dribeas
quelle
2
Ich denke, es ist besser zu klären, dass die oben genannten Beispiele auf Annahmen basieren, wenn Klassen nur polymorph sind, dh mindestens die Basisklasse hat mindestens eine virtuelle Methode.
Irsis
1
Dies schlägt fehl, da Klassen keine polymorphen Typen sind.
Benutzername_4567
4

Das Folgende kommt dem, was Sie von C ++ dynamic_castin Bezug auf die Typprüfung erhalten, nicht wirklich nahe, aber vielleicht hilft es Ihnen, den Zweck ein wenig besser zu verstehen:

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
quelle
3

A dynamic_castführt eine Typprüfung mit RTTI durch . Wenn es fehlschlägt, wird eine Ausnahme ausgelöst (wenn Sie ihm eine Referenz gegeben haben) oder NULL, wenn Sie ihm einen Zeiger gegeben haben.

f4.
quelle
2

Um die dynamische Umwandlung in C-Begriffen zu beschreiben, müssen wir zunächst Klassen in C darstellen. Klassen mit virtuellen Funktionen verwenden eine "VTABLE" von Zeigern auf die virtuellen Funktionen. Kommentare sind C ++. Fühlen Sie sich frei, Kompilierungsfehler neu zu formatieren und zu beheben ...

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

Dann ist eine dynamische Besetzung so etwas wie:

// 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
quelle
1
Die erste Frage war "Können wir ein Äquivalent von dynamic_cast von C ++ in C schreiben".
David Rayna
1

In C gibt es keine Klassen, daher ist es unmöglich, dynamic_cast in dieser Sprache zu schreiben. C-Strukturen haben keine Methoden (daher haben sie keine virtuellen Methoden), daher ist nichts "Dynamisches" darin.

a1ex07
quelle
1

Nein, nicht leicht. Der Compiler weist jeder Klasse eine eindeutige Identität zu. Diese Informationen werden von jeder Objektinstanz referenziert. Diese Informationen werden zur Laufzeit überprüft, um festzustellen, ob eine dynamische Umwandlung zulässig ist. Sie könnten eine Standardbasisklasse mit diesen Informationen und Operatoren erstellen, um die Laufzeitprüfung für diese Basisklasse durchzuführen. Dann würde jede abgeleitete Klasse die Basisklasse über ihren Platz in der Klassenhierarchie informieren und alle Instanzen dieser Klassen könnten über zur Laufzeit umgewandelt werden Ihre Operationen.

bearbeiten

Hier ist eine Implementierung, die eine Technik demonstriert. Ich behaupte nicht, dass der Compiler so etwas verwendet, aber ich denke, es demonstriert die Konzepte:

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
quelle
0

static_cast< Type* >(ptr)

static_cast in C ++ kann in Szenarien verwendet werden, in denen alle Typumwandlungen zur Kompilierungszeit überprüft werden können .

dynamic_cast< Type* >(ptr)

dynamic_cast in C ++ kann verwendet werden, um typsicheres Downcasting durchzuführen . dynamic_cast ist Laufzeitpolymorphismus. Der Operator dynamic_cast, der sicher von einem Zeiger (oder einer Referenz) in einen Basistyp in einen Zeiger (oder eine Referenz) in einen abgeleiteten Typ konvertiert.

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

Für weitere Informationen klicken Sie auf hier

zB 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
quelle