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] 와 같은 캐스팅을 크로스 캐스팅이라고 한다. 별도로 적지는 않았지만 파생 클래스에서 기본 클래스로의 캐스팅을 업 캐스팅이라고 한다. 업 캐스팅은 캐스팅 연산자가 있으나 없으나 안전하게 캐스팅된다.
- 참고 사항 다중 상속이 발생한다면 설계를 다시 검토하라는 말이 있듯이(있나..-_-?) 다중 상속은 그리 권장하지 않는 방식이다.
- 더하기
블로그에
올렸던 글입니다.
|