업 캐스팅 vs. 다운 캐스팅

자식 클래스 포인터 변수가 부모 클래스 포인터로 캐스팅되는 건 정상이다. 업 캐스팅이기 때문이다. 아래 예제는 업 캐스팅 된 부모 클래스지만 오버라이딩 된 자식 클래스 함수가 호출되는 모습이다. (이건 vtable 때문이다. 업 캐스팅과는 무관하다)

#include <cstdio>
#include <cstdlib>

class Parent
{
public:
    virtual void PrintMe() { printf("I am Parent\n"); }
};

class Child : public Parent
{
private:
    int num;

public:
    Child(int anum = 1234) : num(anum) { }
    virtual void PrintMe() { printf("I am Child\n"); }
    void PrintNum() { printf("Hello Child=%d\n", num); }
};
int main()
{
    Child c;
    Parent *p = (Parent*)&c;
    Parent &p2 = (Parent&)c;

    p->PrintMe();
    p2.PrintMe();
    system("pause");

    return 0;
I am Child
I am Child

부모 클래스 포인터 변수가 자식 클래스 포인터로 캐스팅되면? 이걸 다운 캐스팅이라고 하는데, 자식 클래스가 여러 개일 수 있기 때문에 검증이 필요하다. 하지만 안전하기만 하면 상관없다. 이렇게만 하면 아주 잘 된다.

    Parent p;
    Child *c = (Child*)&p;
    Child &c2 = (Child&)p;

    c->PrintMe();
    c2.PrintMe();
I am Parent
I am Parent

이건? 실행은 된다. 값이 이상해서 그렇지. (기본값이 1234 임을 상기하자.) 즉, 그냥 다운 캐스팅을 시도하면 의도치 않은 행동을 할 가능성이 매우 높다.

    c->PrintNum();
    c2.PrintNum();
Hello Child=-858993460
Hello Child=-858993460

dynamic_cast

이걸 피하기 위한 방법으로 쓰는 것이 다이나믹 캐스팅이다.

    Parent p;
    Child *c = dynamic_cast<Child*>(&p);
    Child &c2 = dynamic_cast<Child&>(p);

컴파일은 잘 된다. 실행하면, c 는 NULL 이 되고 c2 는 할당 전에 이미 이런 에러를 낸다. 암튼 bad_cast 라신다.

처리되지 않은 예외 발생(0x00007FF86F9EA388, Template.exe): Microsoft C++ 예외: std::bad_cast, 메모리 위치 0x000000EA4B1DF720.

즉, 실제로 캐스팅하는 객체가 맞을 때만 온전한 포인터를 돌려준다. 꼭 기억해야 한다.

static_cast

지정한 타입으로 변경하는데, 무조건 변경하는 것이 아니라 논리적으로 변환 가능한 타입만 변환한다. 논리적으로 변환이 불가능하면 dynamic_cast 처럼 NULL 을 반환.. 하는게 아니라 아예 컴파일 오류를 낸다.

void main()
{
     char *str="korea";
     int *pi;
     double d=123.456;
     int i;

     i=static_cast<int>(d);                 // 가능
     pi=static_cast<int *>(str);            // 에러
     pi=(int *)str;                         // 가능
}
잘못된 형식 변환입니다. (pi=static_cast<int *>(str);)

두 번째와 세 번째 캐스팅이 차이가 난다. 세 번째는 그냥 str 주소의 위치를 int* 로 반드시 취급하겠다는 개발자의 굳은 의지가 돋보이고, 두 번째는 안전하게 캐스팅을 해서 오참조를 하는 것을 막겠다는 뜻이다.

const_cast

const char *const Parent * 같이 ‘상수 포인터’ 인 경우는,

  1. 포인터 변수의 값 (=주소) 변경이 불가능
  2. 포인터가 가리키는 실제 값의 변경이 불가능

답은 2번이다. (1번을 하려면 char* const 를 해야 한다.)

암튼, 이런 ‘상수 포인터’ 의 제약을 잠시 푸는 것이 const_cast 이다. 그 반대도 물론 가능한데, 그럴 필요가 전혀 없기 때문에 저렇게 이해하는게 좋다.

void main()
{
     char str[]="string";
     const char *c1=str;
     char *c2;

     c2=const_cast<char *>(c1);
     c2[0]='a';
     printf("%s\n",c2);
}
atring

c1 은 ‘상수 포인터’ 기 때문에, c1 을 통해서 str 의 값을 바꿀 수 없다. 하지만 c2const_cast 때문에 이걸 통해서는 바꿀 수가 있단 것이다.

그냥 (char*) 로 써도 될텐데, 이게 왜 필요할까? 나중에 캐스팅 대상 포인터가 변경되면 여기서 에러가 나야 한다. 그냥 포인터 캐스팅을 해 버리면 컴파일 에러가 안 난다.

const char *c1; // 이게 const double *c1 으로 프로그래머가 바꿔버리면? 에러 없다.
char *c2;
c2=(char *)c1;
// c2 = const_cast<char *>(c1); 으로 뒀다면, 여기서 컴파일 에러가 났을 것.