C ++ 'da dynamic_cast ve static_cast


155

dynamic_castC ++ anahtar kelimesi ile oldukça karışık .

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
}

tanım diyor ki:

dynamic_castAnahtar kelime döküm geçerliliğini sağlamak için bir çalışma zamanı kontrolü yaptıktan, başka bir işaretçi veya referans türünden bir referans noktasını atmalarını

Bir dynamic_castşeyi daha iyi anlayabilmem için C + 'ya eşdeğer bir C yazabilir miyiz ?


1
dynamic_cast<>Sahnelerin arkasında nasıl çalıştığına (veya C ++ 'ın ne kadar işe yaradığına) dair iyi bir fikir edinmek istiyorsanız , iyi bir kitap (bu kadar teknik bir şey için de okunması oldukça kolaydır) Lippman'ın "C ++ Nesne Modeli İçinde" dir. Ayrıca Stroustrup'un "C ++ Tasarım ve Evrimi" ve "C ++ Programlama Dili" kitapları iyi kaynaklardır, ancak Lippman'ın kitabı C ++ 'ın' perde arkasında 'nasıl çalıştığına adanmıştır.
Michael Burr

Satırdaki yorum ne anlama B* b2 = dynamic_cast<B*> (ap) // 'b'geliyor? b2 is pointer to bya da ne?
LRDPRDX

@BogdanSikach Bu soru nedir? Basitçe, ap artık bir B sınıfı türüdür

Yanıtlar:


283

İşte static_cast<>ve dynamic_cast<>özellikle işaretçilerle ilgili bir özet . Bu sadece 101 seviyeli bir yıkım, tüm karmaşıklıkları kapsamıyor.

static_cast <Tür *> (ptr)

Bu, işaretçiyi içeri alır ptrve güvenli bir şekilde bir işaretçiye aktarmaya çalışır Type*. Bu döküm derleme zamanında yapılır. Döküm işlemini yalnızca tür türleri ilişkili olduğunda gerçekleştirir. Türler ilişkili değilse, bir derleyici hatası alırsınız. Örneğin:

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 <Tür *> (ptr)

Bu, işaretçiyi tekrar içeri almaya ptrve güvenli bir şekilde bir işaretçiye aktarmaya çalışır Type*. Ancak bu döküm, derleme zamanında değil, çalışma zamanında yürütülür. Bu bir çalışma zamanı döküm olduğundan, özellikle polimorfik sınıflarla birleştirildiğinde yararlıdır. Aslında, certian vakalarda , oyuncuların yasal olabilmesi için sınıfların polimorfik olması gerekir .

Kalıplar iki yönden birine gidebilir: bazdan türetilmiş (B2D) veya türetilmiş bazdan (D2B). D2B yayınlarının çalışma zamanında nasıl çalışacağını görmek yeterince basit. Ya ptrtüretilmiştir Typeya da değildir. D2B dynamic_cast <> s durumunda kurallar basittir. Başka bir şeye herhangi bir şey atmayı deneyebilirsiniz ve eğer ptraslında türetilmişse Type, bir Type*işaretçi geri alırsınız dynamic_cast. Aksi takdirde, bir NULL işaretçisi alırsınız.

Ancak B2D oyuncuları biraz daha karmaşıktır. Aşağıdaki kodu göz önünde bulundurun:

#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 tür bir nesnenin CreateRandom()geri döneceğini söyleyemediğinden , C stili kadro Bar* bar = (Bar*)base;kesinlikle güvenli değildir. Bunu nasıl düzeltebilirsin? Tek yönlü bool gibi bir işlev eklemek olacaktır AreYouABar() const = 0;temel sınıf ve iade truegelen Barve falsegelen Foo. Ancak başka bir yol daha var 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;

}

Yayınlar çalışma zamanında yürütülür ve nesneyi sorgulayarak çalışır (şu an için nasıl endişelenmenize gerek yoktur), aradığımız tür olup olmadığını sorar. Öyleyse, dynamic_cast<Type*>bir işaretçi döndürür; aksi takdirde NULL döndürür.

Bu tabandan türetilmiş dökümün çalışabilmesi için dynamic_cast<>, Standardın polimorfik tipler olarak adlandırdığı Base, Foo ve Bar olmalıdır . Polimorfik bir tip olabilmek için sınıfınızın en az bir virtualişlevi olmalıdır. Sınıflarınız polimorfik türler değilse, tabandan türetilmiş kullanımı dynamic_castderlenmeyecektir. Misal:

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


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

    return 0;
}

Tabana sanal bir dtor gibi bir sanal işlev eklemek hem Base hem de Der polimorfik türlerini yapar:

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
Derleyici neden bu konuda şikayetçi? ve sadece baz için sanal bir traktör sağladığımızda değil mi?
Rika

5
Unutulmamalıdır ki eğer yaparsanız Base* base = new Base;, dynamic_cast<Foo*>(base)olacak NULL.
Yay295

2
@ Coderx7 dynamic_cast yalnızca polimorfik sınıflar, yani en az bir sanal yönteme sahip sınıflar için kullanılabilen Çalışma Zamanı Türü Bilgisine (RTTI) ihtiyaç duyar.
Elvorfirilmathredia

@ Yay295 Neden dynamic_cast<Foo*>(base)bir null Base* base = new Base;?
MuneshSingh

3
@munesh Çünkü basebir Foo. Bir Baseişaretçi a'yı işaret edebilir Foo, ancak yine de bir Foo, bu nedenle dinamik bir yayın çalışır. Bunu yaparsanız Base* base = new Base, basebir olduğunu Base, bir Foodinamik olarak bunu döküm olamaz, bu yüzden Foo.
Yay295

20

Kendi el ile haddelenmiş RTTI'nızı (ve sistemi atlayarak) uygulamadığınız sürece, dynamic_castdoğrudan C ++ kullanıcı düzeyi kodunda uygulamak mümkün değildir . dynamic_castC ++ uygulamasının RTTI sistemine çok bağlıdır.

Ancak, RTTI'yi (ve böylece dynamic_cast) daha fazla anlamanıza yardımcı olmak için <typeinfo>üstbilgiyi ve typeidoperatörü okumalısınız . Bu, elinizdeki nesneye karşılık gelen tür bilgisini döndürür ve bu tür bilgi nesnelerinden çeşitli (sınırlı) şeyleri sorgulayabilirsiniz.


Sizi Wikipedia'ya yönlendiririm, ancak RTTI ile ilgili makaleleri ve dynamic_castçok eksik. :-P Sen asana kadar sadece kendinle oyna. :-)
Chris Jester-Young

10

C kodu daha, bir ingilizce tanımı yeterli olabilir düşünüyorum:

Türetilmiş bir türetilmiş sınıf bulunan bir Base Tabanı verildiğinde, dynamic_castyalnızca işaret edilen gerçek nesne gerçekten türetilmiş bir nesne ise, bir Base işaretçisini Derived pointer'a dönüştürür.

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
}

Örnekte, testfarklı nesneleri başvuruya bağlar Base. Dahili olarak referans, tipik bir şekilde yapılan bir referansa Derivedindirgenir: downcast, yalnızca başvurulan nesnenin gerçekten bir örneği olduğu durumlarda başarılı olur Derived.


2
Yukarıda paylaşılan örneklerin sınıfların sadece polimorfik olması durumunda varsayımlara dayalı olarak çalışacağını açıklığa kavuşturmanın daha iyi olduğunu düşünüyorum, yani en azından Base sınıfının en azından sanal bir yöntemi var.
irsis

1
Sınıflar polimorfik tipler olmadığı için bu başarısız olacaktır.
kullaniciadi_4567

4

Aşağıdakiler, C ++ 'dan dynamic_casttür kontrolü açısından aldıklarınıza gerçekten yakın değildir , ancak belki de amacını biraz daha iyi anlamanıza yardımcı olacaktır:

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 RTTIdynamic_cast kullanarak tip kontrolü yapar . Başarısız olursa, size bir istisna (bir referans verdiyseniz) veya bir işaretçi verdiyseniz NULL atar.


2

İlk olarak, dinamik ifadeyi C terimleriyle tanımlamak için, C'deki sınıfları temsil etmeliyiz. Sanal işlevlere sahip sınıflar, sanal işlevlere "VTABLE" işaretçileri kullanır. Yorumlar C ++. Derleme hatalarını yeniden biçimlendirin ve düzeltin ...

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

O zaman dinamik bir oyuncu şuna benzer:

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

1
İlk soru "C + 'da C ++' nun eşdeğer bir dinamik yayınını yazabilir miyiz?"
David Rayna

1

C'de hiçbir sınıf yoktur, bu nedenle o dilde dynamic_cast yazmak imkansızdır. C yapılarının yöntemleri yoktur (sonuç olarak sanal yöntemlere sahip değildirler), dolayısıyla içinde "dinamik" hiçbir şey yoktur.


1

Hayır, kolay değil. Derleyici, her sınıfa benzersiz bir kimlik atar, bu bilgilere her nesne örneği tarafından başvurulur ve dinamik bir dökümün yasal olup olmadığını belirlemek için çalışma zamanında denetlenir. Bu temel sınıf üzerinde çalışma zamanı denetimi yapmak için bu bilgiler ve işleçlerle standart bir temel sınıf oluşturabilirsiniz, daha sonra türetilmiş herhangi bir sınıf, temel sınıfı sınıf hiyerarşisindeki yerinin bilgisine verir ve bu sınıfların tüm örnekleri, operasyonları.

Düzenle

İşte bir tekniği gösteren bir uygulama. Derleyicinin böyle bir şey kullandığını iddia etmiyorum, ancak kavramları gösterdiğini düşünüyorum:

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)

C ++ içindeki static_cast, tüm döküm türlerinin derleme zamanında doğrulanabileceği senaryolarda kullanılabilir .

dynamic_cast< Type* >(ptr)

C ++ 'daki dynamic_cast, tipte güvenli aşağı döküm gerçekleştirmek için kullanılabilir . dynamic_cast çalışma zamanı polimorfizmidir. İşaretçiden (veya referanstan) temel türe, işaretçiye (veya başvurudan) türetilmiş türe güvenli bir şekilde dönüştüren dynamic_cast işleci.

örneğin 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;
}

Daha fazla bilgi için buraya tıklayın

örneğin 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";
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.