C++의 Type Casting (형 변환)은 값을 한 타입에서 다른 타입으로 변환하는 것. C++은 C-style cast보다 의도가 명확하고 안전한 4가지 전용 연산자를 제공한다.
| Cast | 핵심 용도 | 검사 시점 |
|---|---|---|
static_cast | 일반적인 타입 변환 | Compile Time (컴파일 타임) |
dynamic_cast | 상속 구조에서 안전한 다운캐스팅 | Runtime (런타임) |
const_cast | const / volatile 변경 | 한정자 변경 |
reinterpret_cast | 메모리를 다른 타입으로 재해석 | 실질적 타입 안전성 없음 |
일반적인 타입 변환
→ static_cast
Base → Derived인데 실제 타입을 확인해야 함
→ dynamic_cast
const / volatile만 변경
→ const_cast
메모리/포인터를 완전히 다른 타입으로 해석
→ reinterpret_cast
static = 타입 변환
dynamic = 런타임 타입 확인
const = const 제거/추가
reinterpret = 메모리 재해석
static_cast (정적 형변환)는 Compile Time (컴파일 타임)에 타입 관계를 검사하며, 서로 변환 가능한 타입끼리 명시적으로 변환한다.
float f = 3.5f;
int x = static_cast<int>(f); // 3
주로 숫자 변환, 상속 관계 변환, 사용자 정의 변환 등에 사용한다.
double d = 3.14;
int n = static_cast<int>(d);
double → int처럼 정상적인 타입 변환에 사용한다.
Derived d;
Base* b = static_cast<Base*>(&d); // Upcasting
Upcasting (업캐스팅)은 안전하다.
Downcasting도 문법상 가능하다.
Base* b = ...;
Derived* d = static_cast<Derived*>(b);
하지만 dynamic_cast와 달리 Runtime Type Check (런타임 타입 검사)를 하지 않는다.
즉, 실제 객체가 Derived가 아니면 위험하다.
Base* base = new Derived(); → 문제 없음
Base* base = new Base(); → 문제 있음
생성자나 Conversion Operator (변환 연산자)를 호출할 수도 있다.
string s = static_cast<string>(obj);
해당 타입에 적절한 변환 연산자가 정의되어 있으면 이를 사용한다.
void* 변환int x = 10;
void* p = static_cast<void*>(&x);
int* q = static_cast<int*>(p);
객체 포인터 ↔ void* 변환이 가능하다.
char* c;
int* p = static_cast<int*>(c); // Error
서로 관련 없는 포인터 타입끼리는 변환할 수 없다.
또한:
const 제거 불가 → const_castreinterpret_castdynamic_caststatic_cast
= 컴파일 타임에 유효한 타입 변환인지 검사하는 명시적 형변환
void* 변환dynamic_cast보다 빠르지만 Downcasting 안전성은 직접 보장해야 함dynamic_cast (동적 형변환)는 Runtime Type Checking (런타임 타입 검사)을 수행하는 형변환이다.
주로 Base → Derived Downcasting (다운캐스팅)에 사용한다.
Base* p = new Derived();
Derived* d = dynamic_cast<Derived*>(p);
실제 객체가 Derived인지 런타임에 확인한 뒤 변환한다.
Downcasting에 사용하려면 Base가 Polymorphic Class (다형 클래스)여야 한다.
즉, 최소 하나의 virtual 함수가 필요하다.
class Base {
public:
virtual ~Base() = default;
};
virtual 함수가 없으면 dynamic_cast를 통한 Downcasting은 컴파일 에러가 발생한다.
Base* p = ...;
Derived* d = dynamic_cast<Derived*>(p);
if (d)
// 성공
실제 객체가 Derived이면 정상 포인터를 반환하고, 아니면 nullptr을 반환한다.
Derived1 obj;
Base* p = &obj;
Derived2* d = dynamic_cast<Derived2*>(p);
// d == nullptr
### Reference
```cpp
Derived& d = dynamic_cast<Derived&>(base);
Reference는 nullptr이 될 수 없으므로 실패하면 std::bad_cast Exception (예외)을 발생시킨다.
dynamic_cast는 RTTI (Run-Time Type Information, 런타임 타입 정보)를 이용한다.
dynamic_cast<Derived*>(base);
런타임에 실제 객체 타입을 검사하기 때문에 약간의 실행 비용이 있다.
변환이 확실하게 안전하다는 것을 이미 알고 있다면 static_cast가 더 가볍다.
RTTI (Run-Time Type Information, 런타임 타입 정보)는 실행 중 객체의 실제 타입을 확인하는 기능이다.
주로 Inheritance (상속), Polymorphism (다형성) 환경에서 사용한다.
Derived → Base 변환.
Derived d;
Base* b = &d;
Base → Derived 변환.
Base* b = new Derived;
Derived* d = dynamic_cast<Derived*>(b);
Base 포인터가 실제로 Derived 객체를 가리키는지 확인해야 하므로 Runtime Check (런타임 검사)가 필요하다.
if (d != nullptr)
d->show();
잘못된 Pointer 다운캐스팅이면 nullptr을 반환한다.
RTTI
└─ 실행 중 실제 객체 타입 확인
└─ dynamic_cast 등에 사용
Derived → Base, 안전하며 보통 자동Base → Derived, 실제 타입 확인 필요즉, RTTI = 런타임에 객체의 실제 타입을 파악하기 위한 C++ 기능이다.
Derived* d = dynamic_cast<Derived*>(base);
virtual 함수 필요nullptrstd::bad_caststatic_cast보다 런타임 비용 존재즉, dynamic_cast = 상속 관계에서 실제 객체 타입을 확인하면서 안전하게 형변환할 때 사용한다.
const_cast (상수성 변환)는 타입의 const / volatile 한정자만 추가하거나 제거한다.
const_cast<new_type>(expression)
int x = 10;
const int* p = &x;
int* q = const_cast<int*>(p);
*q = 20; // OK
원본 x가 실제로 non-const이므로 수정 가능하다.
const 객체는 수정 금지const int x = 10;
const int* p = &x;
int* q = const_cast<int*>(p);
*q = 20; // Undefined Behavior
const_cast로 const를 제거하는 것 자체는 가능하지만, Originally Const Object (원래 const 객체)를 실제로 수정하면 Undefined Behavior (정의되지 않은 동작)이다.
void foo(int* p);
const int* p = ...;
foo(const_cast<int*>(p));
주로 const-correctness를 지원하지 않는 Legacy API (레거시 API)와 연동할 때 사용한다. 단, 해당 함수가 실제 값을 수정하지 않는다는 보장이 필요하다.
const Member Function 내부void func() const {
const_cast<MyClass*>(this)->value = 10;
}
const 멤버 함수의 this에서 const를 제거할 수 있다.
단, 실제 객체가 const라면 이를 통해 상태를 수정하는 것은 UB가 될 수 있다.
const int* p;
char* q = const_cast<char*>(p); // Error
const_cast는 타입 자체를 변경할 수 없다.
const 추가/제거volatile 추가/제거만 가능하다.
int x; // 실제 non-const
const int* p = &x;
*const_cast<int*>(p)=1; // OK
const int y = 10; // 실제 const
const int* q = &y;
*const_cast<int*>(q)=1; // UB
const_cast = const/volatile 한정자만 강제로 변경.
가장 중요한 기준은 "포인터가 const인가?"가 아니라 "원본 객체가 실제로 const인가?"이다.
reinterpret_cast (재해석 형변환)는 같은 메모리/주소를 다른 타입으로 해석할 때 사용하는 저수준 캐스팅이다.
int x = 65;
int* p = &x;
char* c = reinterpret_cast<char*>(p);
p와 c는 같은 주소를 가리키지만, 그 메모리를 각각 int, char 관점에서 해석한다.
T* → U*
pointer → integer
integer → pointer
주로 다음과 같은 Low-Level Programming (저수준 프로그래밍)에서 사용한다.
int* p = ...;
char* c = reinterpret_cast<char*>(p);
캐스팅 자체가 가능하다고 해서 해당 포인터를 역참조하는 것까지 안전한 것은 아니다.
A* a = ...;
B* b = reinterpret_cast<B*>(a);
b->foo(); // 실제 메모리가 B가 아니라면 위험 / UB 가능
따라서 static_cast, dynamic_cast, const_cast로 해결할 수 없는 특수한 저수준 상황에서만 사용하는 것이 일반적이다.
reinterpret_cast
= 메모리를 다른 타입의 관점으로 강제로 재해석