[참고] C++ 형변환 연산자 (cast operators), C 형변환 연산자 ()
내용 수준: 초급~중급
표준 C++에서는 4개의 형변환 연산자가 추가되었습니다.
const_cast<>()
static_cast<>()
reinterpret_cast<>()
따라서 C의 형변환 연산자 () 를 포함해서 5개의 형변환 연산자를 사용할 수 있습니다. 이미 많은 사이트에서 논쟁이 있었지만 흔히들 C++에서는 되도록이면 C 형변환 연산자 () 를 사용하지 않도록 권장합니다. 이렇게 C 형변환 연산자 () 대신에 새로운 C++ 형변환 연산자들의 사용을 권장하는 이유로 첫 째, 코드의 호환성이 증대되고 둘 째, 코딩의 의미를 분명히 함으로써 의도하지 않은 오류를 사전에 예방할 수 있다고 합니다.
대부분의 C++ 프로그래머들은 C++ 형변환 연산자들이 프로그램에서 사용될 때 어떠한 역할을 하는지 다들 잘 알고 있겠지만 실재 어떤 상황에서 어떻게 적용되어 지는게 바람직한지 c 형변환 연산자 ()는 왜 나쁘고 이런 상황에서 어떠한 C++ 연산자를 사용해야 하는지 조리있게 조목조목 따져보지는 않는것 같습니다. 여기서는 각각의 C++ 형변환자에 대해서 아주 "간단히" 알아본 후 왜 C 형변환 연산자 () 의 사용이 바람직하지 않은지, 이 때 어떤 C++ 형변환 연산자를 대신 사용하는게 좋은지 생각해봅니다.
dynamic_cast<>()는 RTTI(RunTime Type Information) 흔히 OOP의 다형성의 개념을 구현하기 위해서 C++에서 제공되는 형변환 연산자입니다. 다른 세 가지 형변환 연산자와는 확연히 다른 용도라 할 수 있고 더욱이 C 형변환 연산자 () 와는 별다른 관계가 없다고 볼 수 있습니다. 자세한 내용은 MSDN이나 다른 문서를 참조하세요.
const_cast<>()는 const 나 volatile 조건을 설정/해제하는 형변환을 해줍니다. 다른 C++형변환 연산자들은 절대로 const나 volatile 조건을 해제하는데 사용할 수 없습니다 라는 내용을 일반적인 문서들에서 찾아볼 수 있습니다.
const double d = 20.;
// OK? 아니 컴파일 에러 발생!!! 암시적인 형변환 또는 static_cast<>() 필요하다고 불평.
double d2 = const_cast<double>(d);
double d3 = d; // OK! o.O
double d4 = static_cast<double>(d); // OK! O.o
위의 const_cast<>() 설명을 읽지 않고서 그냥 위의 샘플을 접하면 세 째, 네 째 라인의 대입문이 문제없이 컴파일 실행되는 것이 전혀 이상하지 않았을텐데 일단 const_cast<>() 설명을 읽고나니 이상하군요. 인터넷을 대충 찾아봐도 const_cast<>()가 포인터나 참조랑 같이 사용될 때 const 또는 volatile 조건을 해제한다라는 문구는 찾을 수 없었지만 const_cast<>()를 다음 샘플 코드에서 처럼 사용할 때 const 또는 volatile 조건을 해제할 수 있다는 것을 알 수 있습니다. 포인터 또는 참조가 아닌 데이터 변수(객체)에 직접 const_cast<>()를 사용하여 const 또는 volatile 조건을 제거하려면 컴파일 에러가 발생합니다.
const double d = 20.;
const double *pCD = &d;
double *pD = const_cast<double *>(pCD); // OK!
// 컴파일 에러! static_cast<>()는 const 조건 "해제" 불가능
double *pD2 = static_cast<double *>(pCD);
// OK! static_cast<>()로 const 조건 "설정" 가능
const double *pCD2 = static_cast<const double *>(*pD);
double &rD = const_cast<double &>(d); // OK!
double &rD2 = *const_cast<double *>(&d); // OK!
// 컴파일 에러! *pCD는 const double 형 변수
*pCD = 30.;
// 컴파일 OK! 그러나 상수형 변수의 값을 변경하는 것은 정의되지 않은 행동
rD = 30.;
static_cast<>()는 암시적으로 형변환이 일어나는 경우에 이를 명백하게 형변환을 하겠다라고 분명히 해주는 용도록 사용될 수 있습니다.
code: 다른 한편으로 static_cast<>()는 컴파일 시에 제공되어지는 형정보를 이용하여 가능한 형변환인 경우 데이터의 내부 바이너리 구조를 변경하기도 합니다. 앞선 암시적인 형변환의 경우와는 다르게 이 경우에는 반드시 static_cast<>()를 사용해야 합니다. 마지막으로 위의 경우와 같은 맥락으로 (데이터의 내부 바이너리 구조 변경한다는) 상속관계에서 빈번하게 static_cast<>()를 사용하는 경우는 다중 상속관계에서 다운캐스팅(downcasting)할 때 입니다. 일반적으로 업캐스팅(upcasting)은 항상 안전한 형변환(type safe)이고 암시적으로 형변환이 일어나기 때문에 특별한 형변환 연산자를 사용하지 않아도 문제가 없습니다. 그러나 다운캐스팅 할 때에는 컴파일 시 제공되어지는 형정보를 이용하여 적절한 this 포인터의 계산이 이루어집니다. 즉 내부 바이너리 구조가 변경됩니다. 여기서 dynamic_cast<>()와 static_cast<>()가 다른 점이 static_cast<>()는 사용자가 변환하고자 하는 형과 원래 데이터형이 상속관계에 있기만 하다면 컴파일러는 이러한 사실만을 확인하고 사용자가 제공한 상속 관계가 옳바른 관계인지는 확인하지 않습니다. 반면에 dynamic_cast<>()는 실행 시 실재로 이러한 관계가 옳바른 관계인지 확인하고 이에 따라서 적절한 조치(NULL 리턴)를 합니다.
int n = static_cast<int>(b);
inline TO implicit_cast(const FROM &x) {
return x;
}
bool b = true;
int n = implicit_cast<int>(b); // 암시적인 형변환이 이루이진다는 것을 명백히 해줌.
int n = static_cast<int>(f); // n은 10
};
class B2 {
};
class D1 : public B1 {
};
class D2 : public B1 {
};
B1 *pB1 = implict_cast<B1 *>(new D1());// 컴파일 & 실행 OK!, 업캐스팅, 암시적인 형변환
B2 *pB2 = static_cast<B2 *>(new D1()); // 컴파일 에러! 업캐스팅! 그러나 B2와 D1은 상속관계가 아님
D1 *pD11 = pB1; // 컴파일 에러!, 다운캐스팅 따라서 static_cast<>() 필요
D1 *pD12 = static_cast<D1 *>(pB1); // 컴파일 & 실행 OK! 다운캐스팅
D2 *pD2 = static_cast<D2 *>(pB1); // 컴파일 OK! 그러나 런타임 에러!
// D2와 B1이 상속관계에 있기 때문에 컴파일러는 에러를 발생시키지 않지만
// 실재로 pB1은 D2가 아니라 D1 객체을 가리킴.
reinterpret_cast<>()는 포인터나 정수형 데이터를 다른 임의의 포인터나 정수형으로 변경합니다. 즉 포인터에서 부동소수형(float,double)형 또는 그 반대 방향으로의 형변환에는 사용할 수 없습니다. reinterpret_cast<>()는 데이터의 내부 바이너리 구조를 변경하지 않으며 컴파일 시에 제공되어지는 형변환 정보에 의존하지도 않습니다. reinterpret_cast 라는 이름이 의미하는 그대로 주어진 데이타의 의미를 강제적으로 프로그래머가 원하는, 즉 보통 서로 관련되지 않는, 다른 의미로 해석하겠다는 의지를 나타냅니다. 이는 컴파일러를 속이겠다는 의미라고 볼 수도 있으며 이에 따라서 예측하지 못한 오류가 발생할 위험성이 높아지며 이 때문에 reinterpret_cast를 자주 사용하면 좋지 못한 코딩 습관이라고 하는 이유입니다. 또한 이러한 코드는 컴파일러에 종속되기 쉽상이고 호환성이 떨어지는 프로그램이 됩니다.
C 형변환 연산자 ()는 C++ 형변환 연산자들 중에서 dynamic_cast<>()를 제외한 나머지 3가지 형변환 연산자를 대신하여 사용할 수 있는 만능 형변환 연산자 입니다. 동시에, 그렇기 때문에 사용해서는 안되는 연산자이기도 합니다. 단순하게 C 형변환 연산자 ()를 사용하면 컴파일러가 알아서 static_cast<>(), const_cast<>() 또는 reinterpret_cast<>()가 필요할 각각의 상황에 맞게 변환해줍니다. 경우에 따라서는 2개의 C++ 형변환 연산자를 콤보로 적용하는 효과를 내기도 하는 만능 연산자입니다. 코드를 작성하는 프로그래머 입장에서는 아주 편리하게 사용할 수 있지만 코드를 분석하는 입장에서보면 코드를 작성한 프로그래머의 의도가 무엇인지를 혼돈시키는 가장 큰 장애물로 작용합니다. (초보 프로그래머의 경우는 C 형변환 연산자 ()를 사용하면서 자신이 무엇을 의도했는지 파악하지 못하는 경우도 있을거라 생각합니다.)