C ++의 dynamic_cast 및 static_cast


155

dynamic_castC ++ 의 키워드 와 혼동됩니다 .

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_cast사물을 더 잘 이해할 수 있도록 C 로 동등한 C ++를 작성할 수 있습니까?


1
이면에서 dynamic_cast<>작동 하는 방식 (또는 C ++ 작동 량)에 대한 좋은 아이디어를 얻으려면 Lippman의 "C ++ 개체 모델 내부"라는 좋은 책을 읽어보십시오. 또한 Stroustrup의 "C ++의 디자인과 진화"및 "C ++ 프로그래밍 언어"책은 훌륭한 자료이지만 Lippman의 책은 C ++이 '뒤에서'작동하는 방식에 전념합니다.
Michael Burr

이 줄의 주석은 무엇을 B* b2 = dynamic_cast<B*> (ap) // 'b'의미합니까? b2 is pointer to b또는 무엇을?
LRDPRDX

@BogdanSikach 무슨 질문입니까? 그것은 단순히 AP는 B 클래스의 종류 지금 있다는 것을 의미

답변:


282

여기에 개요입니다 static_cast<>그리고 dynamic_cast<>그들은 포인터에 특별히을 관련이있다. 이것은 101 수준의 런 다운이며 모든 복잡한 사항을 다루지는 않습니다.

static_cast <유형 *> (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 <유형 *> (ptr)

이것은 포인터를 다시 가져 와서 ptr타입 포인터로 안전하게 캐스트합니다 Type*. 그러나이 캐스트는 컴파일 타임이 아니라 런타임에 실행됩니다. 이것은 런타임 캐스트이므로 다형성 클래스와 결합 할 때 특히 유용합니다. 실제로, certian의 경우 캐스트가 합법적이 려면 클래스 가 다형성 이어야합니다 .

캐스트는 기본에서 파생으로 (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에서 Barfalse에서 Foo. 그러나 다른 방법이 있습니다 : use 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;

}

캐스트는 런타임에 실행되며 객체를 쿼리하여 (지금 방법에 대해 걱정할 필요가 없음) 원하는 유형인지 묻습니다. 그렇다면 dynamic_cast<Type*>포인터를 반환합니다. 그렇지 않으면 NULL을 반환합니다.

이 base-to-derived 캐스팅이 dynamic_cast<>, Base, Foo 및 Bar를 사용하여 작동 하려면 Standard가 polymorphic 유형을 호출해야합니다 . 다형성 유형이 되려면 클래스에 virtual함수가 하나 이상 있어야합니다 . 클래스가 다형성 유형이 아닌 경우 기본에서 파생 된 사용은 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 다형성 유형이 모두 생성됩니다.

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
컴파일러가 왜 처음부터 그것에 대해 불평합니까? 기본 전용 가상 dctor 만 제공 할 때도 그렇지 않습니까?
Rika

5
당신이 할 경우 Base* base = new Base;, dynamic_cast<Foo*>(base)될 것 NULL입니다.
Yay295

2
@ Coderx7 dynamic_cast에는 다형성 클래스, 즉 하나 이상의 가상 메소드가있는 클래스에만 사용 가능한 런타임 유형 정보 (RTTI)가 필요합니다.
Elvorfirilmathredia 21

Yay295 왜 @는 dynamic_cast<Foo*>(base)널 (null)는 경우에 Base* base = new Base;?
MuneshSingh

3
@munesh base는 아니기 때문에 Foo. Base포인터가 가리킬 수 있습니다 Foo,하지만 여전히 A의 Foo동적 캐스트가 작동 할 수 있도록. 당신이 할 경우 Base* base = new Base, baseA는 Base아닌 Foo동적으로로 캐스팅 할 수없는, 그래서 Foo.
Yay295

20

직접 롤링 한 RTTI를 구현하지 않고 시스템을 무시하지 않으면 dynamic_castC ++ 사용자 수준 코드로 직접 구현할 수 없습니다 . dynamic_castC ++ 구현의 RTTI 시스템과 밀접한 관련이 있습니다.

그러나 RTTI를 이해하는 데 도움이 dynamic_cast되도록 <typeinfo>헤더와 typeid연산자를 자세히 읽어보십시오 . 이것은 현재 가지고있는 객체에 해당하는 유형 정보를 반환하며 이러한 유형 정보 객체에서 다양한 (제한적) 것들을 조회 할 수 있습니다.


나는 당신에게 Wikipedia를 지적 할 것이지만, RTTI에 관한 기사 dynamic_cast는 매우 불충분합니다. :-P 당신이 그것을 끊을 때까지 혼자서 놀아 라. :-)
Chris Jester-Young

10

C의 코드보다 영어 정의로 충분하다고 생각합니다.

파생 클래스 Derived가있는 Base 클래스가 주어지면 dynamic_cast실제로 가리키는 실제 개체가 실제로 Derived 개체 인 경우에만 Base 포인터를 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형태 보증 방법 : 내리 뜬 만 참조 된 개체가 참의 인스턴스 그 경우에 성공할 것이다 Derived.


2
클래스가 다형성 일 경우, 즉 최소한 Base 클래스에 가상 메서드가 적어도있는 경우 위에서 공유 한 예제가 가정에 따라 작동한다는 것을 분명히하는 것이 좋습니다.
irsis

1
클래스가 다형성 유형이 아니기 때문에 실패합니다.
username_4567

4

다음은 dynamic_cast유형 검사 측면 에서 C ++에서 얻는 것과 실제로 가깝지는 않지만 목적을 조금 더 잘 이해하는 데 도움이 될 것입니다.

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_castRTTI를 사용하여 유형 검사를 수행합니다 . 실패하면 예외 (참조를 제공 한 경우)가 발생하거나 포인터를 제공하면 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
초기 질문은 "C에서 C ++의 dynamic_cast에 해당하는 내용을 작성할 수 있습니까?"였습니다.
David Rayna 1

1

C에는 클래스가 없으므로 해당 언어로 dynamic_cast를 작성할 수 없습니다. C 구조에는 메소드가 없으므로 (가상적으로 가상 메소드가 없음) "동적"이 없습니다.


1

아니, 쉽게 컴파일러는 모든 클래스에 고유 한 ID를 할당합니다.이 정보는 모든 객체 인스턴스에서 참조되며 런타임시 동적 캐스트가 유효한지 확인하기 위해 검사됩니다. 이 정보와 연산자를 사용하여 해당 기본 클래스에서 런타임 검사를 수행하는 표준 기본 클래스를 만들 수 있으며 파생 클래스는 클래스 계층에서 해당 클래스의 위치를 ​​기본 클래스에 알리고 해당 클래스의 모든 인스턴스는 다음을 통해 런타임 캐스팅 가능합니다. 당신의 작업.

편집하다

다음은 하나의 기술을 보여주는 구현입니다. 컴파일러가 이와 같은 것을 사용한다고 주장하지는 않지만 개념을 보여줍니다.

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 ++의 static_cast는 컴파일시 모든 유형 캐스팅을 확인할 수있는 시나리오에서 사용할 수 있습니다 .

dynamic_cast< Type* >(ptr)

C ++의 dynamic_cast를 사용하여 형식 안전 다운 캐스팅 을 수행 할 수 있습니다 . dynamic_cast는 런타임 다형성입니다. 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.