나만의 작은 도서관
[C++] 캐스팅(Casting) 본문
캐스팅이란?
- 캐스팅은 데이터 타입을 다른 데이터 타입으로 변환하는 것을 의미한다.
C 스타일의 캐스팅
T var = (T)expression;
- C++에서도 C 스타일 문법을 사용할 수 있다. C 스타일의 캐스팅은 표현식(expression) 앞에 소괄호로 둘러싼 변환할 타입 T를 적어주는 방식으로 선언한다.
일반 타입(값 타입)으로 캐스팅
int a = (int)b;
- 변환할 타입으로 비트 패턴을 재구성하는 캐스팅.
- 단, 이전 비트 패턴의 의미를 최대한 유지하는 방향으로 재구성한다.
- 큰 타입에서 작은 타입으로 캐스팅할 경우 상위 비트가 잘린다.
- 예를 들어, int → short로 캐스팅할 경우, 상위 비트 2바이트가 잘려 0xffff0000 → 0x0000이 된다.
- 연관 관계가 없는 타입 사이의 캐스팅은 컴파일 오류가 발생한다.
int arr[5] = {1, 2, 3, 4, 5};
// arr[5] -> double
double d = (double)arr; // 컴파일 에러 : 잘못된 형식 변환입니다.
안전한 변환, 안전하지 않은 변환
- "안전한 변환(safe conversion)"은 재구성된 비트 패턴의 의미가 캐스팅 이전과 일치하는 변환을 의미한다.
- 예를 들어, short → int 타입으로 캐스팅하는 경우, short 타입의 값이 4(=0x0004)라면, int 타입의 값도 4(=0x00000004)로 의미가 완전히 일치하므로 “안전한 변환”이다.
- 반대로, "안전하지 않은 변환(unsafe conversion)"은 재구성된 비트 패턴의 의미가 캐스팅 이전과 항상 100% 일치한다고 보장할 수 없는 변환(데이터 손실 발생)을 의미한다.
- 예를 들어, float → int 타입으로 캐스팅하는 경우, float타입의 값이 3.2f 라면, int 타입의 값은 3이므로 의미가 일치하지 않으므로 “안전하지 않은 변환”이다.
- 안전하지 않은 변환은 주로 1) 타입의 종류가 다르거나, 2) 더 작은 크기의 타입으로 캐스팅 시 발생한다.
포인터 타입으로 캐스팅
int* ptr = (int*)ptr2;
- 비트 패턴을 재구성하지 않고 관점만 바꾸는 캐스팅.
- 비트 패턴을 재구성하지 않기 때문에 캐스팅 전과 후의 비트 패턴이 같다.
- 연관 관계가 없는 타입 사이의 캐스팅도 가능하다. (문법상 문제없음)
- 단, 의도치 않은 결과값이 나올 수 있으므로 주의해야 한다.
int num = 65;
// int -> double*
double* ptr = (double*)# // 8.00693e-307 (의도치 않은 결과)
클래스 타입으로 캐스팅
상속 관계인 클래스 사이의 캐스팅
- 일반 타입 캐스팅인 경우, 파생 → 기반(업캐스팅)은 가능하지만 기반→ 파생(다운캐스팅)은 불가능하다.
- 포인터 타입 캐스팅인 경우, 파생 → 기반(업캐스팅)은 가능하지만 기반→ 파생(다운캐스팅)은 명시적인 경우에만 가능하다.
Base b; // Base 클래스 선언
/* ======= Base -> Derived ======*/
// Derived* derivedPtr = &b; // 암시적 다운캐스팅(오류 발생)
Derived* derivedPtr = (Derived*)&b; // 명시적 다운캐스팅 (위험)
상속 관계가 아닌 클래스 사이의 캐스팅
- 일반적으로 불가능하지만, 클래스 내에 타입 변환 연산자를 정의했다면 캐스팅이 가능하다.
타입 변환 연산자
// 클래스 타입 -> T
operator T() const { return data;/*T 타입 멤버 변수*/ }
- 타입 변환 연산자는 특정 클래스가 다른 타입으로 변환될 때 자동 변환을 수행하는 연산자이다.
타입 변환 연산자 예제
#include <iostream>
using namespace std;
class Knight {
public:
int power;
Knight(int p) : power(p) {}
void show() {
cout << "Knight power: " << power << endl;
}
};
class Warrior {
public:
int power = 100;
// 타입 변환 연산자 정의: Warrior -> Knight
operator Knight(){
return Knight(this->power);
}
};
int main() {
Warrior w;
Knight k = w; // Warrior → Knight 변환 연산자 호출
k.show(); // 출력: Knight power: 100
return 0;
}
C++에서의 캐스팅
- 위에서 사용한 C 스타일의 캐스팅 방식( (type)expression )은 1) 아무런 검사 없이 캐스팅되기 때문에 너무 강력하고 위험하며, 사용 목적이 다양하여 해당 캐스팅을 작성한 개발자가 2) 어떤 의도로 사용했는지 판단하기 모호하다는 문제가 있다.
- 이러한 기존 C 스타일 캐스팅의 위험성과 일부 모호함을 제거하기 위해, C++에서는 보다 구체적이고 제한적인 4가지 캐스팅 연산자로 분리하였다. 캐스팅 종류는 아래와 같다.
캐스팅 4총사
- static_cast
- dynamic_cast
- const_cast
- reinterpret_cast
static_cast
T var = static_cast<T>(expression);
- static_cast는 캐스팅 원칙에 비춰보았을 때 상식적인 캐스팅만 허용하는 캐스팅이다.
- 즉, static_cast는 우리가 흔히 생각하는 언어적 차원에서 지원하는 일반적인 캐스팅 방식이다.
- static_cast의 사용 목적은 1) 기본 타입 간의 변환 (int → double, char → int 등), 2) 명시적인 업캐스팅 (Derived* → Base*), 3) void* → T* 변환 등이 있다.
static_cast의 특징
컴파일 타임에 타입 검사
- 컴파일 타임에 캐스팅 작업을 수행한다.
- 잘못된 변환을 막기 위해 컴파일 타임에 타입을 검사하므로 안전하다.
int num = 65;
// int -> char*
char* c = (char*)(num); // 통과됨
char* c = static_cast<char*>(num); // 컴파일 에러 : 잘못된 형식 변환입니다.
static_cast의 다운캐스팅
- 컴파일러는 기본적으로 다운캐스팅을 하지 못하도록 막음에도 불구하고, static_cast는 포인터 타입의 다운캐스팅을 허용한다.
- static_cast의 다운캐스팅은 잘못된 캐스팅이어도 아무 문제 없이 통과되므로 위험하다.
- 따라서 성능상의 이유로 다운캐스팅 시 dynamic_cast를 사용하지 않고 static_cast를 사용했다면, 이때부터 캐스팅 오동작에 대한 책임은 프로그래머가 져야 한다.
class Base {};
class Derived : public Base {};
// 업캐스팅(안전한 변환) : Derived는 항상 Base를 포함하고 있기 때문
Base* base = static_cast<Base*>(new Derived());
// 다운캐스팅(안전하지 않은 변환) : Base는 Derived의 필드와 메서드가 없음.
Derived* derived = static_cast<Derived*>(new Base()); //
dynamic_cast
T var = dynamic_cast<T>(expression);
- dynamic_cast는 상속 관계에 놓인 클래스의 안전한 업/다운캐스팅을 지원하는 캐스팅이다.
- 잘못된 캐스팅인 경우, 캐스팅에 실패하며 nullptr을 반환한다.
- T에는 클래스의 포인터 타입 또는 참조만 들어가며, expression은 T의 타입을 따라간다. (T : 포인터 타입 → expression : 포인터 타입, T : 참조 타입 → expression : l-value)
- dynamic_cast의 주사용 목적은 "안전한 다운캐스팅" (Base* → Derived*)을 하기 위함이다.
- 하지만 성능상의 이유로 다운캐스팅할 때 dynamic_cast대신 static_cast를 사용한다.
dynamic_cast의 특징
dynamic_cast의 기본 패턴
- dynamic_cast는 잘못된 캐스팅을 한 경우 nullptr을 반환하기 때문에 아래와 같이 if-else와 함께 사용하는 패턴이 자주 등장한다.
A* a_ptr = dynamic_cast<A*>(b_ptr); // 캐스팅
if (a_ptr) {
/* 캐스팅 성공 시 실행 */
} else {
/* 캐스팅 실패 시 실행 */
}
런타임에 타입 검사
- dynamic_cast은 static_cast와 달리 RTTI(Runtime Type Information, 런타임 타입 정보)를 사용해 런타임에 타입을 검사한다.
- "RTTI"란 프로그램 실행 중에 객체의 실제 타입을 확인할 수 있도록 하는 기능을 의미한다.
- 따라서, dynamic_cast은 런타임 타입 검사를 통해 static_cast보다 캐스팅의 안전성을 보장한다는 장점이 있지만, RTTI의 추가 리소스 사용에 의해 성능이 static_cast보다 떨어진다는 단점이 있다.
기반 클래스가 다형성 클래스일 때만 동작한다.
- Polymorphic(다형성) 클래스란 하나 이상의 가상 함수를 가진 클래스를 뜻한다.
- dynamic_cast는 가상 함수 테이블(vtable)을 사용하기 때문에 vtable을 생성하는 가상 함수가 하나도 없다면 사용할 수 없다.
class Base {}; // 가상 함수 X
class Derived : public Base {};
Base* base = new Base();
// 런타임 오류: dynamic_cast의 피연산자는 다형 클래스 형식이어야 합니다.
Derived* derived = dynamic_cast<Derived*>(base);
const_cast
T var = const_cast<T>(expression);
- const_cast는 expression의 const 속성을 제거하거나 추가할 수 있는 캐스팅이다.
- const_cast의 주사용 목적은 코드를 수정할 수 없는 특정 API나 함수의 매개변수가 const 속성에 의해 타입 불일치가 발생했을 때 사용한다.
- 사실 거의 사용할 일이 없다.
/* 수정할 수 없는 함수 */
void func(int& x){
cout << "x : " << x << '\\n';
}
const int num1 = 100;
// func(num1); // 컴파일 오류: "int &" 형식에서 "const int" 형식 이니셜라이저로의 바인딩 참조에서 한정자가 삭제되었습니다.
func(const_cast<int&>(num1)); // 출력 결과: x : 100
reinterpret_cast
T var = reinterpret_cast<T>(expression);
- reinterpret_cast는 임의의 포인터 타입으로 변환할 수 있는 캐스팅이다.
- 제약 없이 임의의 타입에 대해 “포인터 타입으로 캐스팅”(비트 패턴을 재구성하지 않고 관점만 바꾸는 캐스팅)을 할 수 있기 때문에, 4가지 캐스팅 중 가장 위험하고 강력한 캐스팅이다.
- reinterpret_cast의 사용목적은 1) 서로 관련 없는 포인터 타입 사이의 캐스팅, 2) 정수를 포인터로 캐스팅(int → void*) 등이 있다.
int num = 65;
char* ptr = reinterpret_cast<char*>(&num);
std::cout << *ptr << std::endl; // A
캐스팅 4총사 정리
캐스팅 종류 | 용도 | 특징 | 안전성 |
static_cast | 일반적인 타입 변환, 업캐스팅 | 컴파일 타임 검사, 안전한 변환 | ✅ 안전 |
dynamic_cast | 다운캐스팅 | 런타임 검사, 실패 시 nullptr 반환 | ✅ 안전 |
const_cast | const 제거 또는 추가 | const 속성 제거 가능 | 약간 위험 (UB 가능성 존재) |
reinterpret_cast | 포인터 타입 변경, 메모리 조작 | 타입 안전성 없음, 위험 | 매우 위험 |
참고 자료
https://www.linkedin.com/pulse/c-typecasting-from-developers-point-view-vishal-chovatiya/
https://musket-ade.tistory.com/entry/C-C-형-변환-연산자-staticcast-constcast-dynamiccast-reinterpretcast
https://hwan-shell.tistory.com/215
https://blockdmask.tistory.com/242
https://learn.microsoft.com/ko-kr/cpp/cpp/static-cast-operator?view=msvc-170
'C++ > 문법 및 메소드(STL)' 카테고리의 다른 글
[C++][Callback] #1. 콜백 함수와 Callable, 그리고 std::function (0) | 2025.04.02 |
---|---|
[C++] 전방 선언(Forward Declaration) (0) | 2025.03.28 |
[C++][Class] 클래스 관련 기타 내용(explicit, mutable, friend 키워드 등...) (0) | 2025.03.26 |
[C++][Class] 오버라이딩과 가상 함수 (0) | 2025.03.26 |
[C++][Class] 상속(Inheritance) (0) | 2025.03.26 |