형 변환이란
형 변환이란 값을 한 형에서 다른 형으로 바꾸는 것을 뜻합니다 — double을 int로, char를 그 숫자 코드로, 또는 기반 클래스 포인터를 파생 클래스 포인터로 바꾸는 것이죠. C++는 이런 변환 중 일부를 자동으로 해주지만, 조용히 처리하는 변환이야말로 버그가 숨는 자리입니다.
종류는 두 가지입니다. 저절로 일어나는 암시적 변환과, 직접 적어 넣는 명시적 캐스트입니다. 그 증상 하나는 연산자에서 이미 만났습니다 — 정수 나눗셈이죠. 캐스트는 그것을 직접 제어하는 방법입니다.
암시적 변환
표현식에서 숫자 형들을 섞으면, C++는 양쪽이 맞도록 "더 작은" 형을 "더 큰" 형으로 승격합니다. 보통은 원하는 대로 동작합니다.
문제는 변환이 반대 방향으로 갈 때 시작됩니다 — 더 넓은 형에서 더 좁은 형으로 갈 때죠. 그것이 축소 변환이며, 조용히 데이터를 잃을 수 있습니다.
float에서 int로의 변환은 0 방향으로 잘립니다 — 반올림하지 않으므로 3.99는 3이 됩니다. 그리고 300을 char에 욱여넣으면 오버플로가 납니다. 많은 컴파일러가 여기서 경고하지만, 일부는 그렇지 않습니다. 정말로 축소할 의도라면, 다음에 읽는 사람이 의도적이었음을 알 수 있도록 캐스트로 명시하세요.
정수 나눗셈 함정 고치기
캐스트를 하는 가장 흔한 이유는 나눗셈입니다. 두 피연산자가 모두 정수일 때, /는 정수 나눗셈을 하고 나머지를 버립니다.
해결책은 나눗셈 전에 피연산자 하나에 static_cast<double>을 적용하는 것입니다. 자주 하는 실수가 static_cast<double>(got / total)인데, 이는 너무 늦습니다. 캐스트가 실행될 때면 got / total은 이미 0이므로 0.0이 나오기 때문입니다. 결과가 아니라 피연산자를 변환하세요.
static_cast: 기본 캐스트
C++는 네 가지 이름 있는 캐스트를 제공합니다. 95%의 경우에 쓰게 될 것은 static_cast<T>(value)로, 연관된 형들 사이의 잘 정의된 변환을 수행합니다 — 숫자 변환, enum에서 int로, void*에서 형이 있는 포인터로의 복귀, 그리고 형을 이미 알고 있을 때 클래스 계층을 위아래로 오가는 변환입니다.
오래된 C 스타일 캐스트 (int)balance보다 static_cast를 택하세요. C 스타일 캐스트는 코드가 컴파일되게 하려고 어떤 변환이든 시도합니다 — 아래의 위험한 것들까지 포함해서요 — 그래서 조용히 const를 떼어내거나 원시 바이트를 재해석할 수 있습니다. static_cast는 컴파일러가 실제로 정당화할 수 있는 변환만 허용하며, 장황한 static_cast<...>는 코드 리뷰에서 찾기가 매우 쉽습니다.
// 피하기 - C 스타일 캐스트, 안전망 없음:
int dollars = (int) balance;
// 권장 - 명시적, 검증됨, 검색하기 쉬움:
int dollars = static_cast<int>(balance);
나머지 세 캐스트 (아껴 쓰기)
나머지 캐스트들은 특정하고 좁은 용도를 위해 존재합니다. static_cast로 정말 할 수 없을 때만 손을 뻗으세요.
**const_cast**는 const를 제거(또는 추가)합니다. 유일하게 정당한 용도는 매개변수에 const 표시를 깜빡한 C 스타일 API를 호출하는 것입니다. 원래 const로 선언된 객체를 const_cast를 통해 수정하는 것은 미정의 동작입니다.
void legacyApi(char* msg); // 오래된 API, const를 받지 않음
const char* text = "hello";
legacyApi(const_cast<char*>(text)); // legacyApi가 거기에 쓰지 않을 때만 안전
**reinterpret_cast**는 원시 비트 패턴을 재해석합니다 — 예를 들어 포인터를 정수 주소로요. 어떤 변환도 하지 않으며 극도로 안전하지 않습니다. 거의 언제나 설계를 다시 생각해야 한다는 신호입니다.
**dynamic_cast**는 객체의 실제 형을 사용하여, 기반 클래스 포인터나 참조를 실행 시점에 안전하게 파생 형으로 변환합니다. 다형적 기반(적어도 하나의 virtual 함수를 가진 클래스)이 필요하며, 캐스트가 적용되지 않으면 nullptr을 반환합니다.
만약 a가 다른 Animal을 가리켰다면, dynamic_cast<Dog*>는 nullptr을 반환하고 else 분기가 실행됩니다 — 바로 이것이 계층을 따라 내려갈 때 무턱대고 static_cast를 쓰는 것보다 안전한 이유입니다.
피해야 할 흔한 실수
- 피연산자가 아니라 결과를 변환하기.
static_cast<double>(a / b)는 먼저 소수부를 버립니다.a나b를 변환하세요. - float에서 int로 반올림된다고 가정하기. 잘립니다.
static_cast<int>(2.99)는2입니다. 반올림하려면std::round,std::lround등을 쓰세요. - C 스타일 캐스트에 손대기. 어떤 변환이 일어나는지 감춥니다.
static_cast를 쓰면 변환이 안전하지 않을 때 조용한 놀람 대신 컴파일 오류를 얻게 됩니다. - 너무 작은 형으로 축소하기.
300을char로, 거대한long을int로 변환하면 둘러싸이거나 오버플로가 납니다. 범위에 충분히 넓은 대상 형을 고르세요.
다음: If-Else
이제 값을 깔끔하게 변환하고 비교할 수 있으니, 다음 단계는 그것으로 결정을 내리는 것입니다. if-else 문은 조건이 true인지에 따라 다른 코드를 실행합니다 — 모든 분기 프로그램의 토대입니다.
자주 묻는 질문
C++에서 static_cast와 C 스타일 캐스트의 차이는 무엇인가요?
(int)x 같은 C 스타일 캐스트는 모든 변환을 차례로 시도합니다 — 알게 모르게 위험한 reinterpret_cast가 되거나 const를 떼어낼 수 있습니다. static_cast<int>(x)는 컴파일러가 검증할 수 있는 연관된 변환만 수행하므로, 컴파일러가 말이 안 되는 변환을 거부합니다. 현대 C++에서는 항상 C 스타일 캐스트보다 static_cast를 택하세요. 더 안전하고 grep으로 찾기도 훨씬 쉽습니다.
C++에서 int를 double로 어떻게 변환하나요?
static_cast<double>(x)를 사용하세요. 이는 나눗셈에서 가장 중요합니다. 5 / 2는 정수 나눗셈이라 2가 나오지만, static_cast<double>(5) / 2는 2.5가 나옵니다. 나눗셈이 일어나기 전에 피연산자 하나를 변환하세요. 결과를 변환하는 static_cast<double>(5 / 2)는 너무 늦어서 여전히 2.0이 됩니다.
C++에서 큰 값을 더 작은 형으로 변환하면 왜 잘못된 숫자가 나오나요?
값을 담을 수 없는 형으로 변환하는 것은 축소 변환입니다. float에서 int로 변환하면 소수부가 잘리고(static_cast<int>(3.99)는 3), 범위를 벗어난 정수는 둘러싸이거나(부호 없음) 구현 정의 동작이 됩니다(부호 있음). 컴파일러는 보통 막아주지 않으므로, 의도적으로 변환하고 대상 형이 충분히 넓은지 확인하세요.