dynamic_cast และ static_cast ใน C ++


155

ฉันค่อนข้างสับสนกับdynamic_castคำหลักใน 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
}

ความหมายพูดว่า:

dynamic_castคำหลักปลดเปลื้องตัวเลขจากตัวชี้หรือการอ้างอิงประเภทหนึ่งไปยังอีกที่มีประสิทธิภาพการตรวจสอบรันไทม์เพื่อให้มั่นใจความถูกต้องของตัวละคร

เราสามารถเขียนdynamic_castC ++ เทียบเท่าใน C เพื่อให้ฉันเข้าใจสิ่งต่าง ๆ ดีขึ้นได้ไหม


1
หากคุณต้องการทราบวิธีการdynamic_cast<>ทำงานเบื้องหลัง (หรือวิธีการทำงานของ C ++) หนังสือดี ๆ (นั่นเป็นเรื่องง่ายที่จะอ่านสำหรับเทคนิคดังนั้น) เป็น Lippman "Inside the C ++ Object Model" นอกจากนี้หนังสือ "การออกแบบและวิวัฒนาการของ C ++" และ "ภาษาการเขียนโปรแกรม C ++" ของ Stroustrup ยังเป็นแหล่งข้อมูลที่ดี แต่หนังสือของ Lippman นั้นอุทิศให้กับการทำงานของ C ++ 'เบื้องหลัง'
Michael Burr

ความคิดเห็นในบรรทัดB* b2 = dynamic_cast<B*> (ap) // 'b'หมายถึงอะไร b2 is pointer to bหรืออะไร?
LRDPRDX

@BogdanSikach คำถามนั้นคืออะไร? มันก็หมายความว่าตอนนี้ ap เป็นประเภทของคลาส B

คำตอบ:


283

นี่คือบทสรุปในstatic_cast<>และdynamic_cast<>โดยเฉพาะอย่างยิ่งเมื่อพวกเขาเกี่ยวข้องกับตัวชี้ นี่เป็นเพียงบทสรุประดับ 101 มันไม่ครอบคลุมความซับซ้อนทั้งหมด

static_cast <Type *> (ptr)

การดำเนินการนี้จะใช้ตัวชี้ptrและพยายามส่งไปยังตัวชี้ชนิดType*อย่างปลอดภัย นักแสดงนี้ทำในเวลารวบรวม มันจะดำเนินการร่ายเมื่อประเภทประเภทที่เกี่ยวข้อง หากประเภทไม่เกี่ยวข้องคุณจะได้รับข้อผิดพลาดของคอมไพเลอร์ ตัวอย่างเช่น:

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)

สิ่งนี้จะพยายามนำตัวชี้เข้ามาอีกครั้งptrและโยนไปยังตัวชี้ชนิดType*อย่างปลอดภัย แต่นักแสดงนี้จะถูกสั่งการตอนรันไทม์ไม่ใช่เวลารวบรวม เนื่องจากนี่เป็นระยะเวลาทำงานจึงมีประโยชน์โดยเฉพาะเมื่อรวมกับคลาส polymorphic ในความเป็นจริงในกรณีใบรับรองคลาสจะต้องมีความหลากหลายเพื่อให้นักแสดงถูกกฎหมาย

การร่ายสามารถไปในหนึ่งในสองทิศทาง: จากฐานสู่มา (B2D) หรือจากมาถึงฐาน (D2B) มันง่ายพอที่จะดูว่า D2B จะทำงานอย่างไรในการรันไทม์ อาจptrมาจากTypeหรือไม่ก็ได้ ในกรณีของ D2B dynamic_cast <> s กฎนั้นง่าย คุณสามารถพยายามที่จะโยนทุกอย่างเพื่อสิ่งอื่นใดและถ้าptrในความเป็นจริงที่ได้มาจากTypeคุณจะได้รับกลับมาชี้จากType* dynamic_castมิฉะนั้นคุณจะได้ตัวชี้ NULL

แต่บรรยากาศของ B2D นั้นซับซ้อนกว่าเล็กน้อย พิจารณารหัสต่อไปนี้:

#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()ไม่สามารถบอกได้ว่าวัตถุชนิดใดCreateRandom()จะกลับมาดังนั้นการเลือกแบบ C Bar* bar = (Bar*)base;จึงไม่ปลอดภัย คุณจะแก้ไขสิ่งนี้ได้อย่างไร วิธีการหนึ่งที่จะเพิ่มฟังก์ชั่นเช่นบูลAreYouABar() const = 0;ไปชั้นฐานและกลับtrueจากBarและจากfalse Fooแต่มีวิธีอื่น: ใช้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;

}

casts รันที่รันไทม์และทำงานโดยการเคียวรีออบเจ็กต์ (ไม่ต้องกังวลเกี่ยวกับวิธีการตอนนี้) โดยถามว่าเป็นประเภทที่เรากำลังมองหาหรือไม่ ถ้าเป็นก็จะdynamic_cast<Type*>ส่งคืนพอยน์เตอร์ มิฉะนั้นจะส่งคืน NULL

ในการสั่งซื้อสำหรับฐานที่จะได้มาจากการหล่อการทำงานโดยใช้dynamic_cast<>ฐานฟูและบาร์จะต้องเป็นสิ่งที่เรียกมาตรฐานประเภท polymorphic เพื่อให้เป็น polymorphic type คลาสของคุณต้องมีอย่างน้อยหนึ่งvirtualฟังก์ชัน หากคลาสของคุณไม่ใช่ประเภท polymorphic การใช้งานพื้นฐานจากที่ได้มาdynamic_castจะไม่รวบรวม ตัวอย่าง:

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


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

    return 0;
}

การเพิ่มฟังก์ชั่นเสมือนลงในฐานเช่น dtor เสมือนจะทำให้ทั้งประเภทฐานและ Der polymorphic:

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
ทำไมคอมไพเลอร์บ่นเกี่ยวกับมันตั้งแต่แรก? และไม่เมื่อเราให้เฉพาะนักแสดงเสมือนจริงสำหรับฐานเท่านั้น?
Rika

5
มันควรจะตั้งข้อสังเกตว่าถ้าคุณทำBase* base = new Base;, จะdynamic_cast<Foo*>(base) NULL
Yay295

2
@ Coderx7 dynamic_cast ต้องการข้อมูลประเภท Run-Time (RTTI) ซึ่งมีเฉพาะสำหรับคลาสที่เป็น polymorphic เช่นคลาสที่มีวิธีเสมือนอย่างน้อยหนึ่งวิธี
Elvorfirilmathredia

@ Yay295 ทำไมจึงdynamic_cast<Foo*>(base)เป็นโมฆะในกรณีของBase* base = new Base;?
MuneshSingh

3
@munesh เพราะไม่ได้เป็นbase ชี้สามารถชี้ไปแต่ก็ยังคงเป็นดังนั้นหล่อแบบไดนามิกจะทำงาน ถ้าคุณทำ, เป็นไม่ได้ดังนั้นคุณจึงไม่สามารถส่งแบบไดนามิกไป FooBaseFooFooBase* base = new BasebaseBaseFooFoo
Yay295

20

หากคุณไม่ได้ติดตั้ง RTTI ที่รีดด้วยมือของคุณเอง (และข้ามระบบหนึ่ง) คุณจะไม่สามารถนำไปใช้dynamic_castโดยตรงในรหัสระดับผู้ใช้ C ++ dynamic_castเชื่อมโยงกับระบบ RTTI ของ C ++ เป็นอย่างมาก

แต่เพื่อช่วยให้คุณเข้าใจ RTTI dynamic_castมากขึ้นคุณควรอ่าน<typeinfo>ส่วนหัวและตัวtypeidดำเนินการ ส่งคืนข้อมูลประเภทที่สอดคล้องกับวัตถุที่คุณมีอยู่และคุณสามารถสอบถามสิ่งต่าง ๆ (จำกัด ) จากวัตถุข้อมูลประเภทเหล่านี้


ฉันจะชี้ให้คุณไปที่ Wikipedia แต่บทความเกี่ยวกับ RTTI และdynamic_castขี้เหนียวมาก :-P เพียงแค่เล่นกับตัวเองจนกว่าคุณจะได้รับมัน :-)
Chris Jester-Young

10

มากกว่ารหัสใน C ฉันคิดว่าคำจำกัดความภาษาอังกฤษอาจเพียงพอ:

ให้คลาสฐานซึ่งมีคลาสที่ได้รับมาdynamic_castจะแปลงตัวชี้ฐานเป็นตัวชี้ Derived ถ้าและถ้าวัตถุจริงชี้ไปที่ในความเป็นจริงเป็นวัตถุที่ได้รับมา

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
}

ในตัวอย่างการเรียกร้องให้ผูกวัตถุที่แตกต่างกันจะมีการอ้างอิงถึงtest Baseภายในอ้างอิงอยู่downcastedการอ้างอิงถึงDerivedในทางที่ typesafe: เศร้าใจจะประสบความสำเร็จเพียง Derivedแต่สำหรับกรณีที่วัตถุอ้างอิงย่อมเป็นตัวอย่างของ


2
ฉันคิดว่ามันจะดีกว่าที่จะอธิบายให้ชัดเจนว่าตัวอย่างที่ใช้ร่วมกันข้างต้นจะทำงานตามสมมติฐานหากคลาสนั้นเป็น polymorphic เท่านั้นนั่นคืออย่างน้อยคลาส Base มีวิธีเสมือนอย่างน้อย
irsis

1
สิ่งนี้จะล้มเหลวเนื่องจากคลาสไม่ใช่ประเภท polymorphic
ชื่อผู้ใช้

4

ต่อไปนี้ไม่ได้ใกล้เคียงกับสิ่งที่คุณได้รับจาก C ++ dynamic_castในแง่ของการตรวจสอบประเภท แต่อาจช่วยให้คุณเข้าใจวัตถุประสงค์ได้ดีขึ้นเล็กน้อย:

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

dynamic_castดำเนินการประเภทการตรวจสอบโดยใช้RTTI ถ้ามันล้มเหลวมันจะทำให้คุณมีข้อยกเว้น (ถ้าคุณให้มันอ้างอิง) หรือ NULL ถ้าคุณให้มันเป็นตัวชี้


2

ก่อนอื่นเพื่ออธิบายการส่งแบบไดนามิกในคำศัพท์ C เราต้องแสดงคลาสใน C คลาสที่มีฟังก์ชันเสมือนใช้ "VTABLE" ของตัวชี้ไปยังฟังก์ชันเสมือน ความคิดเห็นคือ C ++ โปรดฟอร์แมตและแก้ไขข้อผิดพลาดในการคอมไพล์ ...

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

จากนั้นนักแสดงแบบไดนามิกก็มีลักษณะดังนี้:

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

1
คำถามเริ่มต้นคือ "เราสามารถเขียน dynamic_cast ของ C ++ ใน C" ได้ไหม
David Rayna

1

ไม่มีคลาสใน C ดังนั้นจึงเป็นไปไม่ได้ที่จะเขียน dynamic_cast ในภาษานั้น โครงสร้าง C ไม่มีเมธอด (ดังนั้นจึงไม่มีเมธอดเสมือน) ดังนั้นจึงไม่มี "ไดนามิก" อยู่ในนั้น


1

ไม่ไม่ง่ายเลย คอมไพเลอร์กำหนดเอกลักษณ์เฉพาะให้กับทุกคลาสข้อมูลนั้นถูกอ้างอิงโดยทุกอินสแตนซ์ของวัตถุและนั่นคือสิ่งที่ได้รับการตรวจสอบ ณ รันไทม์เพื่อพิจารณาว่าการส่งแบบไดนามิกนั้นถูกกฎหมายหรือไม่ คุณสามารถสร้างคลาสฐานมาตรฐานด้วยข้อมูลและตัวดำเนินการนี้เพื่อทำการตรวจสอบรันไทม์บนคลาสฐานนั้นคลาสที่ได้รับใด ๆ จะแจ้งคลาสพื้นฐานของสถานที่นั้นในลำดับชั้นของคลาสและอินสแตนซ์ใด ๆ ของคลาสเหล่านั้น การดำเนินงานของคุณ

แก้ไข

นี่คือการใช้งานที่แสดงให้เห็นถึงเทคนิคหนึ่ง ฉันไม่ได้อ้างว่าคอมไพเลอร์ใช้อะไรแบบนี้ แต่ฉันคิดว่ามันแสดงให้เห็นถึงแนวคิด:

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

1

dynamic_cast ใช้ RTTI มันอาจทำให้ใบสมัครของคุณช้าลงคุณสามารถใช้การเปลี่ยนแปลงรูปแบบการออกแบบของผู้มาเยี่ยมชมเพื่อให้เกิดความตกต่ำโดยไม่ต้อง RTTI http://arturx64.github.io/programming-world/2016/02/06/lazy-visitor.html


0

static_cast< Type* >(ptr)

static_cast ใน C ++ สามารถนำมาใช้ในสถานการณ์ที่ทุกชนิดหล่อสามารถตรวจสอบได้ที่รวบรวมเวลา

dynamic_cast< Type* >(ptr)

dynamic_cast ใน C ++ สามารถนำมาใช้เพื่อทำการพิมพ์แบบเซฟดาวน์ได้ dynamic_cast เป็น polymorphism เวลาทำงาน ตัวดำเนินการ dynamic_cast ซึ่งแปลงอย่างปลอดภัยจากตัวชี้ (หรือการอ้างอิง) เป็นชนิดพื้นฐานเป็นตัวชี้ (หรือการอ้างอิง) เป็นชนิดที่ได้รับ

เช่น 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;
}

สำหรับข้อมูลเพิ่มเติมคลิกที่นี่

เช่น 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";
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.