'Typecasting'에 해당되는 글 1건

  1. 2007.08.27 C++ 형변환 연산자 (cast operators), C 형변환 연산자 ()

C++ 형변환 연산자 (cast operators), C 형변환 연산자 ()

참고] C++ 형변환 연산자 (cast operators), C 형변환 연산자 ()

내용 수준: 초급~중급


표준 C++에서는 4개의 형변환 연산자가 추가되었습니다.


dynamic_cast<>()

const_cast<>()

static_cast<>()

reinterpret_cast<>()


따라서 C의 형변환 연산자 () 를 포함해서 5개의 형변환 연산자를 사용할 수 있습니다. 이미 많은 사이트에서 논쟁이 있었지만 흔히들

C++에서는 되도록이면 C 형변환 연산자 () 를 사용하지 않도록 권장합니다. 이렇게 C 형변환 연산자 () 대신에 새로운 C++ 형변환 연산

자들의 사용을 권장하는 이유로 첫 째, 코드의 호환성이 증대되고 둘 째, 코딩의 의미를 분명히 함으로써 의도하지 않은 오류를 사전에

예방할 수 있다고 합니다.


대부분의 C++ 프로그래머들은 C++ 형변환 연산자들이 프로그램에서 사용될 때 어떠한 역할을 하는지 다들 잘 알고 있겠지만 실재 어떤

상황에서 어떻게 적용되어 지는게 바람직한지 c 형변환 연산자 ()는 왜 나쁘고 이런 상황에서 어떠한 C++ 연산자를 사용해야 하는지 조

리있게 조목조목 따져보지는 않는것 같습니다. 여기서는 각각의 C++ 형변환자에 대해서 아주 "간단히" 알아본 후 왜 C 형변환 연산자

() 의 사용이 바람직하지 않은지, 이 때 어떤 C++ 형변환 연산자를 대신 사용하는게 좋은지 생각해봅니다.



dynamic_cast 연산자


dynamic_cast<>()는 RTTI(RunTime Type Information) 흔히 OOP의 다형성의 개념을 구현하기 위해서 C++에서 제공되는 형변환 연산자입

니다. 다른 세 가지 형변환 연산자와는 확연히 다른 용도라 할 수 있고 더욱이 C 형변환 연산자 () 와는 별다른 관계가 없다고 볼 수

있습니다. 자세한 내용은 MSDN이나 다른 문서를 참조하세요.


const_cast 연산자


const_cast<>()constvolatile 조건을 설정/해제하는 형변환을 해줍니다. 다른 C++형변환 연산자들절대로 constvolatile

조건을 해제하는데 사용할 수 없습니다 라는 내용을 일반적인 문서들에서 찾아볼 수 있습니다.


const double d = 20.0;

double d2 = const_cast(d); // OK? 아니 컴파일 에러 발생!!! 암시적인 형변환 또는 static_cast<>() 필요하다고 불평.

double d3 = d;                                // OK! o.O

double d4 = static_cast(d); // OK! O.o


위의 const_cast<>() 설명을 읽지 않고서 그냥 위의 샘플을 접하면 세 째, 네 째 라인의 대입문이 문제없이 컴파일 실행되는 것이 전혀 이상하지 않았을텐데 일단 const_cast<>() 설명을 읽고나니 이상하군요. 인터넷을 대충 찾아봐도 const_cast<>()가 포인터나 참조랑 같이 사용될 때 const 또는 volatile 조건을 해제한다라는 문구는 찾을 수 없었지만 const_cast<>()를 다음 샘플 코드에서 처럼 사용할 때 const 또는 volatile 조건을 해제할 수 있다는 것을 알 수 있습니다. 포인터 또는 참조가 아닌 데이터 변수(객체)에 직접 const_cast<>()를 사용하여 const 또는 volatile 조건을 제거하려면 컴파일 에러가 발생합니다.


const double d = 20.0;

const double *pCD = &d;

double *pD = const_cast(pCD);                      // OK!

double *pD2 = static_cast(pCD);                     // 컴파일 에러! static_cast<>()는 const 조건 "해제" 불가능

const double *pCD2 = static_cast(*pD);  // OK! static_cast<>()로 const 조건 "설정" 가능


double &rD = const_cast(d);                          // OK!

double &rD2 = *const_cast(&d);                     // OK!


*pCD = 30.0;                                                                // 컴파일 에러! *pCD는 const double 형 변수

rD = 30.0;                                                                     // 컴파일 OK! 그러나 상수형 변수의 값을 변경하는 것은 정의되지 않은 행동


const_cast<>()를 이용하여 const 조건을 해제할 수 있지만 이 것이 const 변수(객체)의 값을 변경할 수 있다는 것을 의미하지는 않습니다. const 변수(객체)의 값을 const_cast<>()를 이용하여 const 조건 해재 후 변경하는 경우는 C++ 표준에서는 정의되지 않은 행동(undefined behavior)으로 정의 합니다. 쉽게 무슨 일이 일어날지 모릅니다. volatile 조건 역시 const 와 동일한 방법으로 동작합니다.


static_cast 연산자


static_cast<>()는 암시적으로 형변환이 일어나는 경우에 이를 명백하게 형변환을 하겠다라고 분명히 해주는 용도록 사용될 수 있습니

다.


bool b = true;

int n = static_cast(b);


위와 같은 예제에서 굳이 static_cast<>() 를 사용하지 않고 b를 정수형 n에 대입하여도 암시적인 형변환이 자동적으로 이루어집니다.

하지만 위와 같이 명백하게 static_cast<>()를 사용하게 되면 프로그래머의 형변환 의도를 분명하게 나타내주면서 차후에 문제가 생길

지도 모르는 소지를 없애줍니다. C++ 표준에 static_cast<>() 대신에 위와 같은 암시적인 형변환을 나타내는 implicit_cast<>() 연산자

를 포함하자는 주장도 있었다고 합니다. implict_cast<>()는 템플릿을 이용하여 다음과 같이 쉽게 정의할 수 있습니다.


template

inline TO implicit_cast(const FROM &x)

{

 return x;

}


bool b = true;

int n = implicit_cast(b); // 암시적인 형변환이 이루이진다는 것을 명백히 해줌.


다른 한편으로 static_cast<>()는 컴파일 시에 제공되어지는 형정보를 이용하여 가능한 형변환인 경우 데이터의 내부 바이너리 구조를

변경하기도 합니다. 앞선 암시적인 형변환의 경우와는 다르게 이 경우에는 반드시 static_cast<>()를 사용해야 합니다.


float f = 10.0f;

int n = static_cast(f); // n은 10


위의 예제에서 float형 10.0 과 int형 10 의 내부 바이너리 구조는 확연하게 다르다는것을 쉽게 알 수 있죠.


마지막으로 위의 경우와 같은 맥락으로 (데이터의 내부 바이너리 구조 변경한다는) 상속관계에서 빈번하게 static_cast<>()를 사용하

는 경우는 다중 상속관계에서 다운캐스팅(downcasting)할 때 입니다. 일반적으로 업캐스팅(upcasting)은 항상 안전한 형변환(type

safe)이고 암시적으로 형변환이 일어나기 때문에 특별한 형변환 연산자를 사용하지 않아도 문제가 없습니다. 그러나 다운캐스팅 할 때

에는 컴파일 시 제공되어지는 형정보를 이용하여 적절한 this 포인터의 계산이 이루어집니다. 즉 내부 바이너리 구조가 변경됩니다. 여

기서 dynamic_cast<>()static_cast<>()가 다른 점이 static_cast<>()는 사용자가 변환하고자 하는 형과 원래 데이터형이 상속관계에

있기만 하다면 컴파일러는 이러한 사실만을 확인하고 사용자가 제공한 상속 관계가 옳바른 관계인지는 확인하지 않습니다. 반면에

dynamic_cast<>()는 실행 시 실재로 이러한 관계가 옳바른 관계인지 확인하고 이에 따라서 적절한 조치(NULL 리턴)를 합니다.


class B1

{

};


class B2

{

};


class D1 : public B1

{

};


class D2 : public B1

{

};


B1 *pB1 = implict_cast(new D1()); // 컴파일 & 실행 OK!, 업캐스팅, 암시적인 형변환

B2 *pB2 = static_cast(new D1());  // 컴파일 에러! 업캐스팅! 그러나 B2와 D1은 상속관계가 아님

D1 *pD11 = pB1;                                // 컴파일 에러!, 다운캐스팅 따라서 static_cast<>() 필요

D1 *pD12 = static_cast(pB1);      // 컴파일 & 실행 OK! 다운캐스팅

D2 *pD2 = static_cast(pB1);       // 컴파일 OK! 그러나 런타임 에러!

                                                    // D2와 B1이 상속관계에 있기 때문에 컴파일러는 에러를 발생시키지 않지만

                                                    // 실재로 pB1은 D2가 아니라 D1 객체을 가리킴.


reinterpret_cast 연산자


reinterpret_cast<>()는 포인터나 정수형 데이터를 다른 임의의 포인터나 정수형으로 변경합니다. 즉 포인터에서

부동소수형(float,double)형 또는 그 반대 방향으로의 형변환에는 사용할 수 없습니다. reinterpret_cast<>()는 데이터의 내부 바이너리 구

조를 변경하지 않으며 컴파일 시에 제공되어지는 형변환 정보에 의존하지도 않습니다. reinterpret_cast 라는 이름이 의미하는 그대로

주어진 데이타의 의미를 강제적으로 프로그래머가 원하는, 즉 보통 서로 관련되지 않는, 다른 의미로 해석하겠다는 의지를 나타냅니다.

이는 컴파일러를 속이겠다는 의미라고 볼 수도 있으며 이에 따라서 예측하지 못한 오류가 발생할 위험성이 높아지며 이 때문에

reinterpret_cast를 자주 사용하면 좋지 못한 코딩 습관이라고 하는 이유입니다. 또한 이러한 코드는 컴파일러에 종속되기 쉽상이고 호

환성이 떨어지는 프로그램이 됩니다.


C 형변환 연산자 ()


C 형변환 연산자 ()는 C++ 형변환 연산자들 중에서 dynamic_cast<>()를 제외한 나머지 3가지 형변환 연산자를 대신하여 사용할 수 있는

만능 형변환 연산자 입니다. 동시에, 그렇기 때문에 사용해서는 안되는 연산자이기도 합니다. 단순하게 C 형변환 연산자 ()를 사용하면 컴파일

러가 알아서 static_cast<>(), const_cast<>() 또는 reinterpret_cast<>()가 필요할 각각의 상황에 맞게 변환해줍니다. 경우에 따라서

는 2개의 C++ 형변환 연산자를 콤보로 적용하는 효과를 내기도 하는 만능 연산자입니다. 코드를 작성하는 프로그래머 입장에서는 아주 편리하게 사

용할 수 있지만 코드를 분석하는 입장에서보면 코드를 작성한 프로그래머의 의도가 무엇인지를 혼돈시키는 가장 큰 장애물로 작용합니

다. (초보 프로그래머의 경우는 C 형변환 연산자 ()를 사용하면서 자신이 무엇을 의도했는지 파악하지 못하는 경우도 있을거라 생각합니다.)


float f = 10.0f;

int n = (int)f; // static_cast(f)와 동등


// const_cast<>() , reinterpret_cast<>() 콤보

const double d = 20.0;


long *pL = (long *)&d;                                                                  // reinterpret_cast( const_cast(&d) ) 와 동등

long *pL2 = reinterpret_cast( const_cast(&d) );   // (long *) 와 동등


long &rL = (long &)d;                                                                   //

long &rL2 = reinterpret_cast( const_cast(d) );    // (long &) 와 동등


B1 *pB1 = (B1 *)(new D1()); // static_cast(new D1())와 동등, 업캐스팅! B1과 D1은 상속관계

B2 *pB2 = (B2 *)(new D1()); // reinterpret_cast(new D1())와 동등, 업캐스팅! B2와 D1은 상속관계가 아님


D1 *pD12 = (D1 *)(pB1);      // static_cast(pB1)? reinterpret_cast(PB2) ? 컴파일러는 영리하게 static_cast<>() 선택

                                       // this 포인터 계산 (내부 바이너리 구조 변경)

D2 *pD2 = (D2 *)(pB1);       // static_cast(pB1)? reinterpret_cast(PB2) ? 아마도? static_cast<>()


// const_cast<>(), static_cast<>() 콤보

const D1 cD1;


B1 *pB11 = (B1 *)&cD1;                                                     // implicit_cast(const_cast(&cD) ) 와 동등

B1 *pB12 = implicit_cast( const_cast(&cD1) );  // (B1 *) 와 동등


const B1 *pBC11 = (const B1 *)&cD1;                                 //  implicit_cast(&cD) 와 동등

const B1 *pBC12 = implicit_cast(&cD1);            //


D1 *pD11 = (D1 *)pBC11;                                                  // static_cast( const_cast(pBC11) ) 과 동등

D1 *pD12 = static_cast( const_cast(pBC11) ); // (D1 *)와 동등


C 형변환 연산자()를 사용하게 되면 컴파일러는 "우선 const_cast<>()를 적용해야 하는지 결정하고나서 static_cast<>()를 적용할 수 있는지 조사해보고 적용할 수 있으면 static_cast<>()를 적용, 아니면 reinterpret_cast<>()를 적용한다" 라고 생각하면 될 것 같습니다.


따라서 다음과 같이 간략하지만 일부러 꼬아놓은 C++ 문장은 컴파일러가 의외로 많은 함축적인(!) 정해진 작업을 수행 하고 결국 실행 시 오류가 날  수 밖에 없게 됩니다.


const D1 cD1;

D2 *pD21 = (D2 *)(const B1 *)&cD1;


위의 문장은 아마도 아래와 같이 컴파일러가 확장하겠죠.


D2 *pD21 = static_cast( const_cast( implicit_cast(&cD1) ) );


그렇다면 다음 문장은 어떻게? ^^;


D2 &rD21 = (D2 &)cD1;


최근 프로그래밍 추세에 있어서 코드의 가독성(readablity)과 관리용이성(maintenance)은 가장 중요한 요소입니다. 그러한 점에서 상

황에 따라서 제각기 다르게 해석되어질 수 있는 C 형변환 연산자 () 의 사용은 바람직하지 못하다고 할 수 있습니다. 물론 많은 C++ 프로그래머

들이 아직도 C 형변환 연산자 ()를 사용하고 있으며 그 이유는 의외로 간단하다고 생각 합니다.


"C++ 형변환 연산자는 키보드 치기가 너무 힘들어! --;;;"




P.S. 형변환 연산자에 대해 쓰는 김에 마져 덧붙입니다.


간혹 union_cast<>() (또는 이 링크의 글에서는 horrible_cast<>() 라고 합니다) 라는 천하무적 형변환 연산자를 만들어 쓰기도 합니다. 아래와 같이 템플릿을 이용하여 정의되는데 정말로 꼭 필요한 상황이 아니면 절대로 사용하지 말아야 할 연산자입니다. 그냥 이렇게도 사용하는구나 정도만 알고 있으면 될 듯 싶습니다.


template

union horrible_union

{

  TO out;

  FROM in;

};


template

inlineTO horrible_cast(const FROM &x)

{

    horrible_union u;

    // 변환되는 형과 변환하고자 하는 형의 크기가 다르면 컴파일러가 에러를 발생 시키게 함

    typedef int ERROR_CantUseHorrible_cast[sizeof(FROM) == sizeof(u) && sizeof(FROM) == sizeof(TO) ? 1 : -1];

    u.in = x;

    return u.out;

}


union을 이용하여 단순 무식하게 바이너리 비트 복사가 일어나는 것과 같은 형변환 효과를 보여줍니다. 이런 의미에서 union_cast<>() 또는 horrible_cast<>()는  아무런 제약이 없는 (reinterpret_cast<>() 의 경우 부동소수형(float, double)으로의 형변환은 불가능) 진정한 reinterpret_cast 연산자라고 할 수 있습니다. 단 이렇게 union 을 이용하여 형변환을 하는 것은 C++의 형검사(type check) 체계를 뒤흔드는 일일 뿐만 아니라 C 표준에서 조차 정의되지 않은 행동(undefined behavior)으로 정의되어 있다고 합니다. 변환하고자 하는 형과 변환되어지는 형 그리고 union 의 크기를 비교하는 오류 검사를 컴파일 시 수행하기 때문에 그나마(!) 조금 안전하게 사용할 수 있다고는 하지만 여전히 reinterpret_cast<>() 보다, 심지어 C 형변환 연산자 () 보다 백배 천배는 나쁜 녀석(!!!EVIL!!!)입니다.

트랙백 0 댓글 0
prev 1 next