| 캐스팅 연산자 | 사용 목적 | 특징 | 주요 예제 |
|---|---|---|---|
static_cast | 기본 자료형 변환, 업/다운 캐스팅 | 컴파일 타임에 타입 변환, 안전한 변환만 허용 | float -> int, Player* -> Knight* |
dynamic_cast | 상속 관계에서의 안전한 다운 캐스팅 | 런타임에 타입 검사, 실패 시 nullptr 반환 | Player* -> Knight* (RTTI 필요) |
const_cast | const 속성 제거 또는 추가 | const 속성을 변경하는 유일한 캐스팅 | const char* -> char* |
reinterpret_cast | 메모리 주소 변환 | 위험한 변환 허용 (임의의 형변환 가능) | int* -> char*, void* -> Object* |
#include <iostream>
using namespace std;
// 1) 상속 관계에서 업캐스팅/다운캐스팅 실습을 위한 클래스
class Player {
public:
virtual ~Player() {} // RTTI를 위한 가상 소멸자
};
class Knight : public Player {
public:
void Attack() { cout << "Knight Attack!" << endl; }
};
class Archer : public Player {
public:
void Shoot() { cout << "Archer Shoot!" << endl; }
};
class Dog {}; // Player와 상속 관계가 없는 독립적인 클래스
// 2) const_cast 실습을 위한 함수
void PrintName(char* str) {
cout << str << endl;
}
int main() {
// [1] static_cast : 상식적인 타입 변환 (컴파일 타임에 검증됨)
int hp = 100;
int maxHp = 200;
// 정수 나눗셈이므로, 100 / 200 = 0 (부동소수점 변환 필요)
float ratio = static_cast<float>(hp) / maxHp;
cout << "ratio: " << ratio << endl; // 0.5 출력
// 업 캐스팅 (자식 -> 부모) : 컴파일러가 자동으로 변환해줌
Knight* k = new Knight();
Player* p1 = static_cast<Player*>(k);
// 다운 캐스팅 (부모 -> 자식) : 안전성이 보장되지 않음
Player* p2 = new Knight();
Knight* k1 = static_cast<Knight*>(p2); // 가능하지만 위험할 수 있음
// 잘못된 다운 캐스팅 (런타임 오류 가능)
Player* p3 = new Archer();
Knight* k2 = static_cast<Knight*>(p3); // 논리적으로 잘못된 변환 (위험!)
cout << "------------------------------------" << endl;
// [2] dynamic_cast : 상속 관계에서 안전한 다운 캐스팅
// - 가상 함수 테이블(vftable)을 활용한 런타임 타입 체크
Player* p4 = new Knight();
Knight* k3 = dynamic_cast<Knight*>(p4); // 성공 (p4는 Knight 객체)
if (k3) k3->Attack(); // 정상 출력: "Knight Attack!"
Player* p5 = new Archer();
Knight* k4 = dynamic_cast<Knight*>(p5); // 실패 (nullptr 반환)
if (!k4) cout << "p5는 Knight가 아닙니다!" << endl; // 안전하게 확인 가능
cout << "------------------------------------" << endl;
// [3] const_cast : const 제거 (const 데이터를 수정해야 하는 경우 사용)
const char* name = "Hello, World!";
PrintName(const_cast<char*>(name)); // const 속성 제거 후 전달
cout << "------------------------------------" << endl;
// [4] reinterpret_cast : 메모리 주소 변환 (위험한 변환 가능)
__int64 address = reinterpret_cast<__int64>(k3);
cout << "Knight 객체의 주소: " << address << endl;
// 완전히 다른 클래스 간 변환 (위험!)
Dog* dog1 = reinterpret_cast<Dog*>(k3);
// malloc으로 할당한 메모리를 특정 객체로 변환 (주의 필요)
void* p6 = malloc(1000);
Dog* dog2 = reinterpret_cast<Dog*>(p6);
free(p6); // 동적 할당된 메모리 해제
return 0;
}
ratio: 0.5
------------------------------------
Knight Attack!
p5는 Knight가 아닙니다!
------------------------------------
Hello, World!
------------------------------------
Knight 객체의 주소: 129673600
static_cast✔ 컴파일 타임에 타입 변환이 수행됨
✔ 기본 자료형 변환 (int -> float)
✔ 업캐스팅(자식 -> 부모) 가능
✔ 다운캐스팅(부모 -> 자식) 가능하지만 안전성 보장 X
✔ 논리적으로 변환 가능하지만, 런타임 문제 발생 가능
Knight* k = new Knight();
Player* p1 = static_cast<Player*>(k); // 업캐스팅 (안전)
Knight* k1 = static_cast<Knight*>(p1); // 다운캐스팅 (위험!)
🚨 주의:
static_cast는 논리적으로 변환 가능하다고 판단되면 허용하지만, 실제로 잘못된 변환이라도 오류 없이 실행될 수 있다.dynamic_cast를 사용하면 잘못된 변환 시nullptr을 반환하여 안전성을 확보할 수 있다.
dynamic_cast✔ 상속 관계에서 부모 -> 자식 변환 시 안전한 다운 캐스팅
✔ 런타임 타입 체크 (RTTI) 필요 → 실패 시 nullptr 반환
✔ 가상 함수가 있어야 사용할 수 있음 (가상 함수 테이블 활용)
Player* p = new Archer();
Knight* k = dynamic_cast<Knight*>(p); // 실패 시 nullptr 반환
if (!k) {
cout << "p는 Knight가 아닙니다!" << endl;
}
🚨 주의:
dynamic_cast는 가상 함수가 하나 이상 있는 클래스에서만 사용 가능- 잘못된 변환을 방지하기 위해 사용되지만, 성능이 떨어질 수 있음
const_cast✔ const 속성을 제거하거나 추가하는 데 사용
✔ 함수의 매개변수 타입을 변경할 때 유용
✔ 실제 값을 변경하려 하면 "정의되지 않은 동작" 발생 가능
const char* name = "Hello";
PrintName(const_cast<char*>(name)); // const 제거
🚨 주의:
const_cast를 사용하여 상수를 변경하면 프로그램이 크래시할 가능성이 있음.
reinterpret_cast✔ 완전히 다른 타입 간 변환 가능 (위험한 변환 허용)
✔ 포인터 → 정수, 정수 → 포인터 변환 가능
✔ 일반적으로 메모리 주소를 다룰 때 사용됨
Knight* k = new Knight();
__int64 address = reinterpret_cast<__int64>(k);
cout << "Knight 주소: " << address << endl;
🚨 주의:
- 완전히 다른 타입 간 변환도 허용하지만, 오류 발생 가능성이 높음
- 가급적 사용을 피해야 하며, 반드시 필요할 때만 신중하게 사용해야 함