본문 바로가기

진리는어디에/C++

[C++] C++ 스타일 타입 캐스팅

들어가며

C 스타일의 타입 캐스팅은 자유도가 높은 대신 서로 캐스팅될 수 없는 타입임에도 불구하고 암시적으로 캐스팅 해버리고, 그 결과 정의 되지 않은 오류가 발생하게 되고 그 책임을 사용자에게 떠넘겨(?) 버린다. 이런 오류를 미연에 방지하고자 C++에는 새로운 스타일의 타입 캐스팅이 도입 되었고 의도된 대로 사용되지 않는 타입 캐스팅에 대해 컴파일 타임 오류 발생 시키도록 하였다.

이번 포스트에서는 새로이 추가된 C++ 스타일 캐스팅에 대해 살펴 보도록 하겠다.

C++ 스타일 타입 캐스팅 :

  • static_cast
  • const_cast
  • reinterpret_cast
  • dynamic_cast

static_cast

static_cast는 상속 트리 내에서의 포인터형 번환만이 가능한 캐스팅 방법이다. 즉, 상속 트리 내에서 부모나 자식간으로의 포인터 casting만 가능하고 상속 관계가 없는 type으로 casting 하고자 하면 컴파일 타임에 오류가 난다.

class A {
};

class B : public A {
};

class C {
};

A* a = new A;

// B는 A의 자식이므로 casting이 가능하다. 또한 그 반대로도 가능하다.
B* b = static_cast<B*>(a);
A* aa = static_cast<A*>(b);

// 컴파일 오류
C* c = static_cast<C*>(a);

한가지 참고 할 것으로, static_cast는 다이아몬드형 상속구조에서는 정상적으로 동작하지 않는다. 예를 들어 아래와 같은 코드를 살펴 보자.

class A {
public :
    virtual ~A() {}
};

class B : public A {
public :
    virtual ~B() {}
};

class C : public A {
public :
    virtual ~C() {}
};

class D : public B, C {
public :
    virtual ~D() {}
};

D* d1 = new D();
A* a1 = static_cast<A*>(d1);

A* a2 = new A();
D* d2 = static_cast<D*>(a2);

위의 코드에서 컴파일러는 D의 기반 클래스인 A를 어떤 것으로 정해야 할지 갈피를 잡지 못하고 에러를 발생 시킨다. 이럴 경우에는 아래에서 소개될 dynamic_cast를 사용해야만 한다.

const_cast

const_cast는 서로 다른 형간의 변환을 수행하지는 못하지만, 대신 const가 아니었던것을 const로 만들거나, const였던것을 non-const로 만들 수 있다. 하지만 일반적으로 non-const type을 const로 바꾸는 경우는 거의 없으며 그리 제한적인 변경이 아니므로 자동적인 type casting이 일어난다. 하지만 const를 non-const type으로 변경 하는 것은 명시적으로 지정 해 주어야만 한다.

class A {
};

class B : public A{
};

int main() {

   A* a = new A;
   // 컴파일 오류 invalid const_cast from type `A*' to type `B*'
   B* b = const_cast<B*>(a);

    const int ci = 10;
    int* pi = const_cast<int*>(&ci);
    *pi = 1000;

    std::cout << pi << " " << *pi << std::endl;
    std::cout << &ci << " " <<  ci << std::endl;
}

※ 신기한 것은 같은 주소 공간에 서로 다른 한정자가 지정된 값을 가지고 있다는 것이다. 이것을 어떻게 설명 해야 하는 것인고..

reinterpret_cast

C style type casting과 똑같은 위력을 발휘한다. type, 상속 관계에 관계 없이 어떤 내장형, 어떤 포인터형이든지 다 casting이 가능하다.

※ 각종 예외상황들은 해당 컴파일러에 따라 다르게 처리된다.

dynamic_cast

다른 casting과 dynamic_cast의 차이점은 다른 여타 cast 연산자들은 컴파일 타임에 컴파일러에 의해 평가되며, 정상적인 컴파일 또는 오류로 결과가 나타난다. 하지만 dynamic_cast는 casting의 가능 여부를 런타임에 평가하며 내장 테이터 형을 제외한 포인터나 참조에만 이용 할 수 있다.

dynamic_cast는 static_cast와 달리 두 포인터가 동일한 상속 트리에 있는지 확인하지 않으며 포인터가 참조하는 개체의 실제 형을 확인하고 변환이 가능한지를 확인한다. 만일 가능하다면 다중 상속을 처리하기 위한 오프셋 계산까지 마친 새로운 포인터를 리턴하고, 불가능 하다면 NULL 포인터를 리턴한다.

보다 자세한 설명을 위해 아래의 예를 살펴 보도록 하자.

class Parent
{
public :
    virtual void polymorphic() = 0;
};

class DeriveA : public Parent
{
public :
    virtual void polymorphic() {
    }
};

class DeriveB : public Parent
{
public :
    virtual void polymorphic() {
    }
};

int main(int argc, char** argv)
{
    Parent* p =  new DeriveA;
    DeriveB* db = dynamic_cast<DeriveB*>(p);
    if(NULL == db)
    {
        std::cout << "It is not a instance of DeriveB" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveB" << std::endl;
    }

    DeriveA* da = dynamic_cast<DeriveA*>(p);
    if(NULL == da)
    {
        std::cout << "It is not a instance of DeriveA" << std::endl;
    }
    else
    {
        std::cout << "It is a instance of DeriveA" << std::endl;
    }

    return 0;
}

※ dynamic_cast를 사용하기 위해서는 컴파일러의 RTTI(real time type infomation) 옵션을 켜놓아야 한다.

위의 코드를 컴파일 하여 결과를 살펴 보면 :

It is not a instance of DeriveB
It is a instance of DeriveA

와 같이 나올 것이다.

요약을 하자면,  DeriveA의 인스턴스는 다형성을 이용하기 위해 부모 클래스의 포인터를 이용하고 있고, dynamic_cast는 런타임시에 어떤 자식 클래스의 인스턴스인지 부모 클래스의 포인터를 통해서 알아 낼 수 있는 것이다.

다이나믹 캐스트에 대한 보다 자세한 설명은 다음 링크 참조 : http://www.cprogramming.com/reference/typecasting/dynamiccast.html

 

Dynamic-Cast Typecast - C/C++ Syntax Reference - Cprogramming.com

Dynamic-cast Typecast Dynamic casts are only available in C++ and only make sense when applied to members of a class hierarchy ("polymorphic types"). Dynamic casts can be used to safely cast a superclass pointer (or reference) into a pointer (or reference)

www.cprogramming.com

부록 1. 같이 읽으면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!