dynamic_cast dan static_cast dalam C ++


155

Saya cukup bingung dengan dynamic_castkata kunci dalam 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
}

definisi mengatakan:

Kata dynamic_castkunci melemparkan datum dari satu pointer atau tipe referensi ke yang lain, melakukan pemeriksaan runtime untuk memastikan validitas para pemain

Bisakah kita menulis yang setara dengan dynamic_castC ++ di C sehingga saya bisa lebih memahami hal-hal?


1
Jika Anda ingin mendapatkan ide bagus tentang bagaimana cara dynamic_cast<>bekerja di belakang layar (atau seberapa banyak C ++ bekerja), buku yang bagus (itu juga cukup mudah dibaca untuk sesuatu yang sangat teknis) adalah Lippman "Inside the C ++ Object Model". Juga buku-buku "Desain dan Evolusi C ++" Stroustrup dan "Bahasa Pemrograman C ++" adalah sumber yang bagus, tetapi buku Lippman didedikasikan untuk bagaimana C ++ bekerja 'di belakang layar'.
Michael Burr

Apa yang dimaksud dengan komentar di baris B* b2 = dynamic_cast<B*> (ap) // 'b'? b2 is pointer to batau apa?
LRDPRDX

@BogdanSikach Pertanyaan apa itu? Ini berarti ap sekarang menjadi tipe kelas B

Jawaban:


282

Berikut ini ikhtisar static_cast<>dan dynamic_cast<>khususnya terkait dengan pointer. Ini hanya jadwal 101 tingkat, tidak mencakup semua seluk-beluk.

static_cast <Type *> (ptr)

Ini mengambil pointer ptrdan mencoba dengan aman melemparkannya ke pointer tipe Type*. Pemeran ini dilakukan pada waktu kompilasi. Ini hanya akan melakukan pemeran jika jenisnya terkait. Jika jenisnya tidak terkait, Anda akan mendapatkan kesalahan kompiler. Sebagai contoh:

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

Ini lagi mencoba untuk mengambil pointer ptrdan dengan aman melemparkannya ke pointer tipe Type*. Tetapi pemeran ini dieksekusi saat runtime, bukan waktu kompilasi. Karena ini adalah pemeran run-time, ini berguna terutama ketika dikombinasikan dengan kelas polimorfik. Faktanya, dalam kasus-kasus tertentu kelas-kelas tersebut harus polimorfik agar para pemain menjadi legal.

Pemain dapat masuk dalam salah satu dari dua arah: dari pangkalan ke turunan (B2D) atau dari turunan ke pangkalan (D2B). Ini cukup sederhana untuk melihat bagaimana gips D2B akan bekerja saat runtime. Entah ptrberasal dari Typeatau tidak. Dalam kasus D2B dynamic_cast <> s, aturannya sederhana. Anda dapat mencoba untuk melemparkan apa pun ke hal lain, dan jika ptrmemang berasal dari Type, Anda akan mendapatkan Type*pointer kembali dynamic_cast. Jika tidak, Anda akan mendapatkan pointer NULL.

Tapi gips B2D sedikit lebih rumit. Pertimbangkan kode berikut:

#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()tidak tahu jenis objek apa yang CreateRandom()akan kembali, sehingga para pemeran C-style Bar* bar = (Bar*)base;jelas bukan tipe-safe. Bagaimana Anda bisa memperbaikinya? Salah satu caranya adalah dengan menambahkan fungsi seperti bool AreYouABar() const = 0;ke kelas dasar dan kembali truedari Bardan falsedari Foo. Tetapi ada cara lain: gunakan 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;

}

Para pemain mengeksekusi pada saat runtime, dan bekerja dengan menanyakan objek (tidak perlu khawatir tentang bagaimana untuk sekarang), menanyakannya apakah itu tipe yang kita cari. Jika ya, dynamic_cast<Type*>kembalikan pointer; selain itu mengembalikan NULL.

Agar casting berbasis-ke-turunan ini dapat bekerja menggunakan dynamic_cast<>, Base, Foo dan Bar harus sesuai dengan apa yang disebut Standar sebagai tipe polimorfik . Untuk menjadi tipe polimorfik, kelas Anda harus memiliki setidaknya satu virtualfungsi. Jika kelas Anda bukan tipe polimorfik, penggunaan basis-ke-turunan dynamic_casttidak akan dikompilasi. Contoh:

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


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

    return 0;
}

Menambahkan fungsi virtual ke basis, seperti virtual dtor, akan membuat tipe Base dan Der polimorfik:

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
Mengapa kompiler mengeluh tentang hal itu di tempat pertama? dan tidak ketika kami hanya menyediakan dctor virtual untuk basis saja?
Rika

5
Perlu dicatat bahwa jika Anda melakukannya Base* base = new Base;, dynamic_cast<Foo*>(base)akan NULL.
Yay295

2
@ Coderx7 dynamic_cast membutuhkan Run-Time Type Information (RTTI) yang hanya tersedia untuk kelas yang polimorfik, yaitu kelas dengan setidaknya satu metode virtual.
Elvorfirilmathredia

@ Yay295 Mengapa dynamic_cast<Foo*>(base)null dalam kasus a Base* base = new Base;?
MuneshSingh

3
@unun Karena basebukan Foo. Sebuah Basepointer dapat menunjuk ke Foo, tapi itu masih Foo, jadi cast dinamis akan bekerja. Jika ya Base* base = new Base, baseadalah a Base, bukan a Foo, jadi Anda tidak dapat secara dinamis mengirimkannya ke a Foo.
Yay295

20

Kecuali jika Anda menerapkan RTTI lintingan tangan Anda sendiri (dan melewati sistem satu), tidak mungkin untuk menerapkan dynamic_castsecara langsung dalam kode tingkat pengguna C ++. dynamic_castsangat terikat dengan sistem RTTI implementasi C ++.

Tetapi, untuk membantu Anda lebih memahami RTTI (dan karenanya dynamic_cast), Anda harus membaca di <typeinfo>header, dan typeidoperator. Ini mengembalikan info jenis yang sesuai dengan objek yang Anda miliki, dan Anda dapat menanyakan berbagai hal (terbatas) dari objek info jenis ini.


Saya akan mengarahkan Anda ke Wikipedia, tetapi artikelnya tentang RTTI dan dynamic_castsangat minim. :-P Hanya bermain sendiri sampai Anda terbiasa. :-)
Chris Jester-Young

10

Lebih dari kode dalam C, saya pikir definisi bahasa Inggris sudah cukup:

Diberikan Basis kelas yang darinya terdapat kelas turunan Turun, dynamic_castakan mengonversi bilangan penunjuk Basis ke penunjuk Berasal jika dan hanya jika objek aktual yang ditunjuki sebenarnya adalah objek Turunan.

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
}

Dalam contoh, panggilan untuk testmengikat objek yang berbeda ke referensi Base. Secara internal referensi diturunkan ke referensi dengan Derivedcara yang aman: downcast akan berhasil hanya untuk kasus-kasus di mana objek yang direferensikan memang merupakan contoh Derived.


2
Saya pikir lebih baik untuk mengklarifikasi bahwa contoh yang dibagikan di atas akan bekerja berdasarkan asumsi jika kelas hanya polimorfik yaitu setidaknya kelas Base memiliki setidaknya metode virtual.
irsis

1
Ini akan gagal karena kelas bukan tipe polimorfik.
username_4567

4

Berikut ini tidak benar-benar dekat dengan apa yang Anda dapatkan dari C ++ dynamic_castdalam hal pengecekan tipe tetapi mungkin ini akan membantu Anda memahami tujuannya sedikit lebih baik:

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_castmelakukan pengecekan tipe menggunakan RTTI . Jika gagal, itu akan memberi Anda pengecualian (jika Anda memberikan referensi) atau NULL jika Anda memberikannya pointer.


2

Pertama, untuk menggambarkan pemeran dinamis dalam istilah C, kita harus mewakili kelas dalam C. Kelas dengan fungsi virtual menggunakan "VTABLE" dari pointer ke fungsi virtual. Komentar adalah C ++. Jangan ragu untuk memformat ulang dan memperbaiki kesalahan kompilasi ...

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

Maka pemain dinamis adalah sesuatu seperti:

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

1
Pertanyaan awal adalah "Bisakah kita menulis setara dengan dynamic_cast dari C ++ di C".
David Rayna

1

Tidak ada kelas dalam C, jadi tidak mungkin menulis dynamic_cast dalam bahasa itu. Struktur C tidak memiliki metode (akibatnya, mereka tidak memiliki metode virtual), jadi tidak ada yang "dinamis" di dalamnya.


1

Tidak, tidak mudah. Kompiler memberikan identitas unik untuk setiap kelas, bahwa informasi direferensikan oleh setiap instance objek, dan itulah yang diperiksa pada saat runtime untuk menentukan apakah pemain dinamis legal atau tidak. Anda bisa membuat kelas dasar standar dengan informasi ini dan operator untuk melakukan pemeriksaan runtime pada kelas dasar itu, maka setiap kelas turunan akan menginformasikan kelas dasar tempatnya di hierarki kelas dan setiap instance dari kelas-kelas tersebut akan runtime-castable via operasi Anda.

sunting

Berikut ini adalah implementasi yang menunjukkan satu teknik. Saya tidak mengklaim kompiler menggunakan sesuatu seperti ini, tapi saya pikir itu menunjukkan konsep:

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 dalam C ++ dapat digunakan dalam skenario di mana semua tipe casting dapat diverifikasi pada waktu kompilasi .

dynamic_cast< Type* >(ptr)

dynamic_cast dalam C ++ dapat digunakan untuk melakukan tipe safe down casting . dynamic_cast adalah menjalankan polimorfisme waktu. Operator dynamic_cast, yang secara aman mengkonversi dari pointer (atau referensi) ke tipe dasar ke pointer (atau referensi) ke tipe turunan.

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

Untuk informasi lebih lanjut klik di sini

mis. 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";
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.