CS/C#_C++

C++ 캐스팅 연산자

tae-woong 2025. 10. 7. 22:24

C++에서 캐스팅(Casting)은 특정 타입의 변수나 객체를 다른 타입으로 변환하는 과정을 말합니다. C++은 C언어 스타일의 캐스팅 외에도, 더 명시적이고 안전한 4가지의 캐스팅 연산자를 제공합니다. 이 연산자들은 개발자의 의도를 명확하게 드러내고, 컴파일 타임이나 런타임에 타입 검사를 수행하여 잠재적인 오류를 줄여줍니다.

 

C 스타일 캐스팅의 문제점

C언어에서 사용하던 방식은 다음과 같습니다.

// (type)expression
int value = (int)float_value;

이 방식은 편리하지만, 몇 가지 문제점을 가지고 있습니다.

  • 가독성 저하 : 코드 전체에서 캐스팅이 일어나는 부분을 찾기 어렵습니다.
  • 위험성 : 컴파일러가 타입 변환의 안정성을 제대로 검사하지 못하는 경우가 많습니다. 예를 들어, 관련 없는 포인터 타입끼리 변환하거나, const 속성을 제거하는 등의 위험한 변환도 무분별하게 허용합니다.
  • 의도 불명확 : 어떤 종류의 캐스팅(단순 형변환, 포인터 변환 등)이 일어나는지 명확히 알기 어렵습니다.

이러한 문제점을 해결하기 위해 C++에서는 4가지 목적별 캐스팅 연산자를 도입했습니다.

 

C++ 캐스팅 연산자 종류

1) static_cast

  • 개념 : 컴파일 타임에 타입 변환의 유효성을 검사하며, 논리적으로 변환 가능한 타입 간의 변환에 사용됩니다. 가장 일반적인 캐스팅 연산자입니다.
  • 사용 경우:
    • int를 float으로, float을 double으로 바꾸는 등 기본적인 숫자 타입 간의 변환
    • 상속 관계에 있는 클래스 간의 포인터/참조 변환 (다운캐스팅 시에는 안전성 보장 X)
    • void*를 다른 타입의 포인터로 변환
int i = 10;
float f = static_cast<float>(i); // 기본 타입 변환

class Base {};
class Derived : public Base {};
Derived* d = new Derived();
Base* b = static_cast<Base*>(d); // 업캐스팅

 

2) dynamic_cast

  • 개념 : 런타임에 타입 변환의 유효성을 검사하며, 주로 상속 관계에 있는 클래스 간의 다운캐스팅에 안전하게 사용됩니다.
  • 특징:
    • 런타임에 RTTI(Run-Time Type Information)를 사용하여 실제 객체의 타입을 확인합니다.
    • 다운캐스팅이 유효하지 않으면 포인터의 경우 nullptr를, 참조의 경우 std::bad_cast 예외를 발생시킵니다.
    • 가상 함수가 하나 이상 포함된 다형성(Polymorphic) 클래스에만 사용할 수 있습니다.
  • 사용 경우:
    • 부모 클래스 포인터가 실제로 자식 클래스 객체를 가리키는지 확인하고 안전하게 다운캐스팅할 때 사용합니다.
class Base { public: virtual ~Base() {} };
class Derived : public Base {};
Base* b = new Derived();

Derived* d = dynamic_cast<Derived*>(b);
if (d) {
    // 캐스팅 성공!
} else {
    // 캐스팅 실패! b는 Derived 객체를 가리키고 있지 않음
}

 

3) const_cast

  • 개념 : 변수의 const 또는 volatile 한정자를 제거할 때 사용합니다.
  • 주의 : const 객체를 const_cast로 변환하여 값을 수정하려고 시도하는 것은 미정의 동작(Undefined Behavior)을 유발하므로 매우 위험합니다. 원래 const가 아니었던 변수를 가리키는 const 포인터/참조에서 const를 제거할 때만 안전하게 사용할 수 있습니다.
  • 사용 경우:
    • const 멤버 함수에서 non-const 멤버 변수에 접근해야 하는 경우 (좋은 설계는 아님)
    • const 한정자를 인자로 받지 않는 레거시 API에 변수를 전달해야 할 때
void print(int* p) { /* ... */ }

const int val = 10;
const int* p_val = &val;
// print(p_val); // 컴파일 에러
print(const_cast<int*>(p_val)); // 컴파일은 되지만, print 함수 내에서 값을 바꾸면 UB

 

4) reinterpret_cast

  • 개념 : 비트 수준의 재해석을 통해 전혀 관련 없는 타입 간의 변환을 수행합니다. 가장 위험한 캐스팅 연산자입니다.
  • 특징 :
    • 포인터를 정수로, 정수를 포인터로 바꾸거나, 완전히 다른 타입의 포인터로 변환하는 등 매우 낮은 수준의 변환을 허용합니다.
    • 컴파일러는 거의 아무런 검사를 하지 않으며, 이식성이 떨어지고 버그 발생 가능성이 매우 높습니다.
  • 사용 경우:
    • 메모리 주소를 직접 다루는 하드웨어 관련 코드나 저수준 프로그래밍에서 제한적으로 사용됩니다.
int i = 42;
int* p_i = &i;
// 포인터 주소를 정수로 저장
uintptr_t addr = reinterpret_cast<uintptr_t>(p_i);

// 정수를 다시 포인터로 변환
int* p_restored = reinterpret_cast<int*>(addr);