dynamic_cast i static_cast w C ++


155

Jestem dość zdezorientowany ze dynamic_castsłowem kluczowym w 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
}

definicja mówi:

dynamic_castKluczowe rzuca punkt odniesienia z jednego wskaźnika lub odniesienia do innego typu, przeprowadza kontrolę wykonania w celu zapewnienia ważności obsady

Czy możemy napisać odpowiednik dynamic_castC ++ w C, abym mógł lepiej zrozumieć rzeczy?


1
Jeśli chcesz się dobrze zorientować, jak dynamic_cast<>działa za kulisami (lub jak dużo C ++ działa), dobrą książką (która jest również dość łatwa do przeczytania w przypadku czegoś tak technicznego) jest „Inside the C ++ Object Model” Lippmana. Również książki Stroustrupa „Design and Evolution of C ++” i „The C ++ Programming Language” są dobrymi zasobami, ale książka Lippman jest poświęcona temu, jak C ++ działa „za kulisami”.
Michael Burr,

Co oznacza komentarz w linii B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to balbo co?
LRDPRDX

@BogdanSikach Co to za pytanie? Oznacza to po prostu, że ap jest teraz typem klasy B

Odpowiedzi:


282

Oto podsumowanie, static_cast<>a dynamic_cast<>konkretnie, jak odnoszą się one do wskaźników. To tylko lista 101 poziomów, nie obejmuje wszystkich zawiłości.

static_cast <Type *> (ptr)

To pobiera wskaźnik ptri próbuje bezpiecznie rzutować go na wskaźnik typu Type*. To rzutowanie jest wykonywane w czasie kompilacji. Wykonuje rzutowanie tylko wtedy, gdy typy typów są powiązane. Jeśli typy nie są powiązane, pojawi się błąd kompilatora. Na przykład:

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)

To ponownie próbuje wziąć wskaźnik ptri bezpiecznie rzucić go na wskaźnik typu Type*. Ale to rzutowanie jest wykonywane w czasie wykonywania, a nie w czasie kompilacji. Ponieważ jest to rzutowanie w czasie wykonywania, jest przydatne zwłaszcza w połączeniu z klasami polimorficznymi. W rzeczywistości w pewnych przypadkach klasy muszą być polimorficzne, aby obsada była legalna.

Rzuty mogą przebiegać w jednym z dwóch kierunków: od podstawy do pochodnej (B2D) lub od pochodnej do podstawy (D2B). Wystarczy zobaczyć, jak rzutowanie D2B będzie działać w czasie wykonywania. Albo ptrpochodzi z, Typealbo nie. W przypadku D2B dynamic_cast <> s, zasady są proste. Możesz spróbować rzucić cokolwiek na cokolwiek innego, a jeśli ptrfaktycznie zostało wyprowadzone z Type, otrzymasz Type*wskaźnik z powrotem dynamic_cast. W przeciwnym razie otrzymasz wskaźnik NULL.

Ale rzuty B2D są trochę bardziej skomplikowane. Rozważ następujący kod:

#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()nie można powiedzieć, jaki rodzaj obiektu CreateRandom()zwróci, więc rzutowanie w stylu C Bar* bar = (Bar*)base;zdecydowanie nie jest bezpieczne dla typów. Jak mogłeś to naprawić? Jednym ze sposobów byłoby dodać funkcję jak bool AreYouABar() const = 0;do klasy bazowej i powrót truez Bari falsez Foo. Ale jest inny sposób: użyj 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;

}

Rzuty są wykonywane w czasie wykonywania i działają poprzez odpytywanie obiektu (na razie nie trzeba się martwić, jak to zrobić), pytając, czy jest to typ, którego szukamy. Jeśli tak, dynamic_cast<Type*>zwraca wskaźnik; w przeciwnym razie zwraca NULL.

Aby to rzutowanie z bazy na wyprowadzenie działało przy użyciu dynamic_cast<>, Base, Foo i Bar muszą być tym, co Standard nazywa typami polimorficznymi . Aby być typem polimorficznym, Twoja klasa musi mieć co najmniej jedną virtualfunkcję. Jeśli Twoje klasy nie są typami polimorficznymi, użycie metody bazowej na pochodne dynamic_castnie zostanie skompilowane. Przykład:

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


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

    return 0;
}

Dodanie funkcji wirtualnej do bazy, takiej jak wirtualny dtor, spowoduje utworzenie polimorficznych typów Base i 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
Dlaczego kompilator narzeka na to w pierwszej kolejności? i czy nie, gdy zapewniamy tylko wirtualnego reżysera tylko dla bazy?
Rika,

5
Należy zauważyć, że jeśli to zrobisz Base* base = new Base;, dynamic_cast<Foo*>(base)będzie NULL.
Yay295

2
@ Coderx7 dynamic_cast wymaga Run-Time Type Information (RTTI), które jest dostępne tylko dla klas polimorficznych, tj. Klas z co najmniej jedną metodą wirtualną.
Elvorfirilmathredia

@ Yay295 Dlaczego dynamic_cast<Foo*>(base)jest null w przypadku Base* base = new Base;?
MuneshSingh

3
@munesh Ponieważ basenie jest Foo. BaseWskaźnik może wskazywać Foo, ale to wciąż Footak oddanych dynamiczne będą działać. Jeśli to zrobisz Base* base = new Base, basejest a Base, a nie a Foo, więc nie możesz dynamicznie rzutować go na plik Foo.
Yay295

20

Jeśli nie zaimplementujesz własnego, ręcznie rozwijanego RTTI (i pominiesz systemowy), nie jest możliwe zaimplementowanie dynamic_castbezpośrednio w kodzie C ++ na poziomie użytkownika. dynamic_castjest bardzo mocno powiązany z systemem RTTI implementacji C ++.

Ale aby pomóc ci zrozumieć RTTI (a tym samym dynamic_cast) więcej, powinieneś przeczytać <typeinfo>nagłówek i typeidoperatora. To zwraca informacje o typie odpowiadające obiektowi, który masz pod ręką, i możesz zapytać o różne (ograniczone) rzeczy z tych obiektów informacyjnych typu.


Wskazałbym na Wikipedię, ale jej artykuły na temat RTTI dynamic_castsą bardzo skąpe. :-P Po prostu baw się z tym sam, aż to zrozumiesz. :-)
Chris Jester-Young

10

Więcej niż kod w C, myślę, że wystarczyłaby angielska definicja:

Biorąc pod uwagę klasę Base, w której istnieje klasa pochodna Derived, dynamic_castkonwertuje wskaźnik Base na wskaźnik Derived wtedy i tylko wtedy, gdy wskazany obiekt jest w rzeczywistości obiektem pochodnym.

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
}

W tym przykładzie wywołanie testwiąże różne obiekty z odwołaniem do Base. Wewnętrznie odwołanie jest redukowane do odwołania do Derivedw sposób bezpieczny dla typów: downcast zakończy się sukcesem tylko w tych przypadkach, w których obiekt, do którego istnieje odwołanie, jest rzeczywiście wystąpieniem Derived.


2
Myślę, że lepiej jest wyjaśnić, że przykłady udostępnione powyżej będą działać w oparciu o założenia, jeśli klasy są tylko polimorficzne, tj. Przynajmniej klasa podstawowa ma przynajmniej metodę wirtualną.
irsis

1
To się nie powiedzie, ponieważ klasy nie są typami polimorficznymi.
username_4567

4

Poniższe nie są zbyt bliskie temu, co otrzymujesz z C ++ dynamic_castw zakresie sprawdzania typów, ale może pomoże ci trochę lepiej zrozumieć jego cel:

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_castprzeprowadza kontrolę typu przy użyciu RTTI . Jeśli się nie powiedzie, zgłosi wyjątek (jeśli dałeś mu odniesienie) lub NULL, jeśli dałeś mu wskaźnik.


2

Po pierwsze, aby opisać rzutowanie dynamiczne w języku C, musimy przedstawić klasy w języku C. Klasy z funkcjami wirtualnymi używają „TABELI VTABLE” wskaźników do funkcji wirtualnych. Komentarze są w C ++. Zapraszam do ponownego formatowania i naprawiania błędów kompilacji ...

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

Wtedy dynamiczna obsada to coś takiego:

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

1
Początkowe pytanie brzmiało „Czy możemy napisać odpowiednik dynamic_cast z C ++ w C”.
David Rayna,

1

W C nie ma klas, więc nie można napisać dynamic_cast w tym języku. Struktury C nie mają metod (w rezultacie nie mają metod wirtualnych), więc nie ma w tym nic „dynamicznego”.


1

Nie, niełatwo. Kompilator przypisuje każdej klasie unikalną tożsamość, do informacji odwołuje się każda instancja obiektu i właśnie to jest sprawdzane w czasie wykonywania w celu ustalenia, czy rzutowanie dynamiczne jest legalne. Możesz utworzyć standardową klasę bazową z tymi informacjami i operatorami, aby przeprowadzić inspekcję w czasie wykonywania tej klasy bazowej, wtedy każda klasa pochodna poinformuje klasę bazową o swoim miejscu w hierarchii klas, a wszelkie wystąpienia tych klas będą rzutowane w czasie wykonywania za pośrednictwem Twoje operacje.

edytować

Oto implementacja, która demonstruje jedną technikę. Nie twierdzę, że kompilator używa czegoś takiego, ale myślę, że demonstruje koncepcje:

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 w C ++ może być używany w scenariuszach, w których rzutowanie wszystkich typów można zweryfikować w czasie kompilacji .

dynamic_cast< Type* >(ptr)

Dynamic_cast w C ++ może służyć do wykonywania bezpiecznego rzutowania typu . dynamic_cast to polimorfizm w czasie wykonywania. Operator dynamic_cast, który bezpiecznie konwertuje ze wskaźnika (lub odwołania) do typu podstawowego na wskaźnik (lub odwołanie) do typu pochodnego.

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

Aby uzyskać więcej informacji, kliknij tutaj

np. 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";
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.