Dev

C++ 이야기 - [2] 캐스트 연산자 dynamic_cast

prostars 2007. 10. 9. 12:07
C++의 4가지 캐스트 연산자에 대한 이야기 중 두 번째다.
이번은 그중에서 dynamic_cast 에 대해서 이야기한다.

dynamic_cast 는 상속 관계 안에서 포인터나 참조자의 타입을 기본 클래스에서 파생 클래스로의 다운 캐스팅과 다중 상속에서 기본 클래스 간의 안전한 타입 캐스팅에 사용된다.
안전한 타입 캐스팅이란 런타임에 타입 검사를 한다는 것이며 아래에 조금 더 자세하게 나온다.
const_cast와 같이 다른 용도로는 사용하지 못하며 용도가 명확하다.
참고로 dynamic_cast 를 사용하려면 기본적으로 다형성은 이해를 하고 있어야 하며 RTTI도 이해하고 있다면 이 글을 볼 필요가 없을 것이다.
객체가 위치한 메모리의 시작부분을 찾는 데도 사용된다는데 사용해 본 적이 없다.
객체를 void* 로 dynamic_cast 하면 시작 주소가 나온다.
[예] void* p = dynamic_cast<void*>( ObjectPointer );

- dynamic_cast 사용
dynamic_cast 를 사용하기 전에 제약 사항을 확인하자.
나름 제약 사항이 많다.
  • 상속 관계 안에서만 사용할 수 있다.
  • 하나 이상의 가상함수를 가지고 있어야 한다.
  • 컴파일러의 RTTI 설정이 켜져 있어야 한다.
[예제 1]
class Base
{
public :
    virtual void Put( void ) { cout << "Base" << endl; }
};

class Derived : public Base
{
public :
    void Put( void ) { cout << "Derived" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    Base* pBase = new Base;
    Base* pDerived1 = new Derived;
    Derived* pDerived2 = new Derived;
    Derived* pDerived = NULL;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pDerived = pBase;

    // 컴파일 성공 : 런타임에 타입 변환에 실패하며 널을 리턴한다.
    pDerived = dynamic_cast<Derived*>( pBase );       
    if ( pDerived == NULL )
        cout << "Runtime Error" << endl;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pDerived = pDerived1;

    // 컴파일 성공 : 런타임에 타입 변환에 성공하며 Derived 타입의 포인터를 리턴한다.
    pDerived = dynamic_cast<Derived*>( pDerived1 );
    if ( pDerived )
        pDerived->Put();

    // 컴파일 성공 : 이런 경우에는 캐스팅이 필요 없다.
    pDerived = pDerived2;
}
위의 [예제 1] 에서 dynamic_cast 의 기본 동작을 볼 수 있다.
[예제 1] 에서 중요한 내용은 'pDerived = dynamic_cast<Derived*>( pBase );' 에서 볼 수 있듯이 포인터가 실제로 가리키는 대상이 기본 클래스의 객체라면 변환은 실패한다는 것이다.
dynamic_cast 가 캐스팅해주는 것은 포인터나 참조자의 타입을 다운 캐스팅하는 것이지 객체의 타입을 캐스팅하지는 못한다.

dynamic_cast 는 캐스팅에 실패할 때 대상이 포인터라면 널을 리턴하고 참조자였다면 bad_cast 예외를 던진다.
이것이 위에서 언급한 '안전한 타입 캐스팅'의 의미다.
A에서 B로 포인터의 타입을 캐스팅하는 것이 문제없는지 런타임에 검사하여 처리할 수 있다.

다중 상속의 상황에서 기본 클래스 간의 타입 캐스팅을 보자.
[예제 2]
class BaseOne
{
public :
    virtual void Put( void ) { cout << "BaseOne" << endl; }
};

class BaseTwo
{
public :
    virtual void Put( void ) { cout << "BaseTwo" << endl; }
};

class Derived : public BaseOne, public BaseTwo
{
public :
    void Put( void ) { cout << "Derived" << endl; }
};

int _tmain(int argc, _TCHAR* argv[])
{
    BaseOne* pBaseOne = NULL;
    BaseTwo* pBaseTwo = new Derived;

    // 컴파일 오류 : 타입 변환을 할 수 없다.
    //pBaseOne = pBaseTwo;

    // 컴파일 성공 : 런타임에 타입 변환에 성공하며 BaseOne 타입의 포인터를 리턴한다.
    pBaseOne = dynamic_cast<BaseOne*>( pBaseTwo );
    if ( pBaseOne )
        pBaseOne->Put();
   
    return 0;
}
[예제 2] 는 아래 [그림 1] 처럼 다중 상속 관계의 클래스 구성에서 기본 클래스 간의 타입 캐스팅을 보여준다.

사용자 삽입 이미지

[예제 2] 와 같은 캐스팅의 한 사용 예로는 각 기본 클래스 포인터(또는 참조자) 타입의 컨테이너 혹은 배열을 운용하면서 서로 간의 요소를 교환할 때 사용할 수 있다.

[예제 1] 과 같은 캐스팅을 다운 캐스팅이라고 하며 [예제 2] 와 같은 캐스팅을 크로스 캐스팅이라고 한다.
별도로 적지는 않았지만 파생 클래스에서 기본 클래스로의 캐스팅을 업 캐스팅이라고 한다.
업 캐스팅은 캐스팅 연산자가 있으나 없으나 안전하게 캐스팅된다.

- 참고 사항
다중 상속이 발생한다면 설계를 다시 검토하라는 말이 있듯이(있나..-_-?) 다중 상속은 그리 권장하지 않는 방식이다.
반응형