반응형

잘 정리된 포스트

const_cast 는 포스트 하지 않았는데 간단하기 때문이다

상수성 const 를 제거한다


아래는 포스트 내용

 

 

http://prostars.tistory.com/55 

 

 

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] 와 같은 캐스팅을 크로스 캐스팅이라고 한다.
별도로 적지는 않았지만 파생 클래스에서 기본 클래스로의 캐스팅을 업 캐스팅이라고 한다.
업 캐스팅은 캐스팅 연산자가 있으나 없으나 안전하게 캐스팅된다.

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

 


http://prostars.tistory.com/65  중에서..

 

 - reinterpret_cast 사용
reinterpret_cast 를 사용하기 전에 용도와 제약 사항을 확인하자.
전혀 관계없는 타입 간의 변환
상속관계가 없는 클래스 간의 변환 포함
const_cast 의 기능은 수행하지 못함

 

 


http://prostars.tistory.com/64

 

++의 4가지 캐스트 연산자에 대한 이야기 중 세 번째다.
이번은 그중에서 static_cast 에 대해서 이야기한다.

static_cast 는 기본적으로 C 스타일의 캐스팅과 가장 비슷한 기능을 한다.
물론 C 스타일의 캐스팅처럼 만능은 아니다. 4가지의 캐스트 연산자로 분리된 만큼 const_cast의 역할인 상수성을 날린다거나 하는 등의 다른 캐스트 연산자의 고유 기능은 수행하지 못한다.
다른 캐스트 연산자와 같이 static_cast 도 static_cast 만의 용도가 있다.

- static_cast 사용
static_cast 를 사용하기 전에 용도와 제약 사항을 확인하자.
실수형과 정수형, 정수형과 열거형등의 기본 데이터 타입 간의 변환
상속관계의 클래스 계층 간의 변환
런타임 타입 검사를 하지 않음
다형성이 없어도 변환 가능 (RTTI 옵션이 꺼져있어도 된다)
다중 상속에서 기본 클래스 간의 타입 변환은 못함
void 포인터를 다른 타입의 포인터로 변환
서로 다른 타입의 포인터 간의 타입 변환은 못함

서로 다른 타입의 포인터 간의 타입 변환에 reinterpret_cast 를 사용하기보다는 void 포인터를 경유하는 방식을 추천한다.

[예제 1]
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[])
{
    int IntValue = 0;
    double DoubleValue = 100.0;

    // 암시적 캐스팅 : 경고 발생
    IntValue = DoubleValue;
    // 명시적 캐스팅 : 경고 없음
    IntValue = static_cast<int>( DoubleValue );

    // 컴파일 오류 : 변환할 수 없는 타입
    //char* pChar = static_cast<char*>( &IntValue );

    // 컴파일 성공 : void 포인터에서 변환은 가능
    void* pVoid = &IntValue;
    char* pChar = static_cast<char*>( pVoid );

    BaseOne* pBaseOne = NULL;
    BaseTwo* pBaseTwo = new Derived;
    Derived*    pDerived = NULL;

    // 컴파일 성공 : 계층간 타입 변환이 가능 (타입 안전 검사는 안함)
    pDerived = static_cast<Derived*>( pBaseTwo );

    // 컴파일 오류 : 변환할 수 없는 타입 (dynamic_cast 필요)
    //pBaseOne = static_cast<BaseOne*>( pBaseTwo );   

    return 0;
}
위의 [예제 1] 에서 static_cast 의 기본 동작을 볼 수 있다.
[예제 1] 의 'pDerived = static_cast<Derived*>( pBaseTwo );' 에서 타입 변환은 컴파일 타임에 끝난다.
pBaseTwo 에 NULL 이 들어가 있어도 컴파일은 성공한다.
즉, 변환하려는 포인터가 계층 관계에 있다면 어떤 값을 가지고 있던지 컴파일은 성공한다는 말이다.
문제는 런타임에 발생할 것이고 대부분 프로그램의 다운으로 이어질 것이다.
이런 문제점 때문에 클래스 계층 간의 타입 변환에 dynamic_cast 를 추천하는 것이다.

[예제 1] 에는 재미있는 내용이 하나 있다.
서로 다른 타입의 포인터 간의 변환은 금지지만 void 포인터를 경유한다면 가능하다.
'char* pChar = static_cast<char*>( &IntValue );' 이것은 컴파일이 안되지만,
'void* pVoid = &IntValue;
 char* pChar = static_cast<char*>( pVoid );' 이것은 컴파일이 된다. 물론 포인터 값도 정상적으로 넘어온다.

 

반응형

+ Recent posts