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<>()는 const 나 volatile 조건을 설정/해제하는 형변환을 해줍니다. 다른 C++형변환 연산자들은 절대로 const나 volatile
조건을 해제하는데 사용할 수 없습니다 라는 내용을 일반적인 문서들에서 찾아볼 수 있습니다.
const double d = 20.0;
double d2 = const_cast
double d3 = d; // OK! o.O
double d4 = static_cast
위의 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
double *pD2 = static_cast
const double *pCD2 = static_cast
double &rD = const_cast
double &rD2 = *const_cast
*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
위와 같은 예제에서 굳이 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
다른 한편으로 static_cast<>()는 컴파일 시에 제공되어지는 형정보를 이용하여 가능한 형변환인 경우 데이터의 내부 바이너리 구조를
변경하기도 합니다. 앞선 암시적인 형변환의 경우와는 다르게 이 경우에는 반드시 static_cast<>()를 사용해야 합니다.
float f = 10.0f;
int n = static_cast
위의 예제에서 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
B2 *pB2 = static_cast
D1 *pD11 = pB1; // 컴파일 에러!, 다운캐스팅 따라서 static_cast<>() 필요
D1 *pD12 = static_cast
D2 *pD2 = static_cast
// 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
// const_cast<>() , reinterpret_cast<>() 콤보
const double d = 20.0;
long *pL = (long *)&d; // reinterpret_cast
long *pL2 = reinterpret_cast
long &rL = (long &)d; //
long &rL2 = reinterpret_cast
B1 *pB1 = (B1 *)(new D1()); // static_cast
B2 *pB2 = (B2 *)(new D1()); // reinterpret_cast
D1 *pD12 = (D1 *)(pB1); // static_cast
// this 포인터 계산 (내부 바이너리 구조 변경)
D2 *pD2 = (D2 *)(pB1); // static_cast
// const_cast<>(), static_cast<>() 콤보
const D1 cD1;
B1 *pB11 = (B1 *)&cD1; // implicit_cast
B1 *pB12 = implicit_cast
const B1 *pBC11 = (const B1 *)&cD1; // implicit_cast
const B1 *pBC12 = implicit_cast
D1 *pD11 = (D1 *)pBC11; // static_cast
D1 *pD12 = static_cast
C 형변환 연산자()를 사용하게 되면 컴파일러는 "우선 const_cast<>()를 적용해야 하는지 결정하고나서 static_cast<>()를 적용할 수 있는지 조사해보고 적용할 수 있으면 static_cast<>()를 적용, 아니면 reinterpret_cast<>()를 적용한다" 라고 생각하면 될 것 같습니다.
따라서 다음과 같이 간략하지만 일부러 꼬아놓은 C++ 문장은 컴파일러가 의외로 많은 함축적인(!) 정해진 작업을 수행 하고 결국 실행 시 오류가 날 수 밖에 없게 됩니다.
const D1 cD1;
D2 *pD21 = (D2 *)(const B1 *)&cD1;
위의 문장은 아마도 아래와 같이 컴파일러가 확장하겠죠.
D2 *pD21 = static_cast
그렇다면 다음 문장은 어떻게? ^^;
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
// 변환되는 형과 변환하고자 하는 형의 크기가 다르면 컴파일러가 에러를 발생 시키게 함
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!!!)입니다.
'Computing' 카테고리의 다른 글
SATA2 하드디스크의 NCQ 기능 사용하기 (0) | 2007.10.16 |
---|---|
Microsoft Robotics Studio에서 XInputController (0) | 2007.10.10 |
Ubiquitous 연구 동향 (0) | 2007.05.30 |
안티스파이웨어 랭킹 리뷰 (0) | 2007.05.22 |
samba에서 user (0) | 2007.05.17 |