Casting
static_cast
- 논리 형변환
- 주로 Scalar Type들 간의 캐스팅
- 형 변환 함수가 정의되어 있으면 이 함수를 호출하면서 실행
- 연산자 오버로딩을 통하여 변환 가능한 자료형을 추가할 수 있다
- 컴파일 타임에서 캐스팅
// 커스텀 클래스들 선언
class MyFloat
{
public:
float Real;
};
class MyInt
{
public:
int Num;
// 형 변환 함수
operator MyFloat(void)
{
MyFloat f;
f.Real = static_cast<float>(Num);
return f;
};
};
int main()
{
MyInt i = { 5 };
MyFloat f = { 0.f };
// 정상적으로 변환 수행
// 형 변환 함수가 있기 때문
f = static_cast<MyFloat>(i);
return 0;
}
reinterpret_cast
- 강제 형변환
- Pointer Casting
- 포인터와 포인터, 또는 포인터와 일반 자료 끼리의 형 변환으로 주로 사용
- static_cast는 하나의 함수 호출이지만, reinterpret_cast는 해당 자료형을 다음 자료형으로 인식하라라는 메세지를 컴파일러에게 전달하는 역할
// 아래 코드의 결과는
int i = 5;
float *pF = new float;
// memcpy는 메모리를 일방적으로 복사하는 함수
// 두 번째 인수에서 첫번째 인수로 사이즈(세번째 인수) 만큼 복사 진행
memcpy(pf, &i, sizeof(float) < sizeof(int) ? sizeof(float) : sizeof(int));
cout << *pf;
delete pf;
// 이 코드와 같다
int i = 5;
float pf = *(reinterpret_cast<float *>(&i));
cout << pf;
// 올바른 결과를 출력하지는 못한다
const_cast
- non const -> const
- 그냥 const 키워드를 써라
- const -> non const
- 잠재 위험이 너무 많다
- 포인터 형 뿐만 아니라 레퍼런스 형에도 사용가능하다
// const_cast
#include <iostream>
using namespace std;
void print (char * str)
{
cout << str << '\n';
}
// 아래 print 함수는 정상적으로 작동한다
// constless object를 read만 하고, write을 하지 않기 땨문
// constless object에 write하는 건 undefined behavior를 유발
// c가 const 이기 대문
int main () {
const char * c = "sample text";
print ( const_cast<char *> (c) );
return 0;
}
Usage
// 어떤 클래스에서 string의 index에 해당하는 char을 반환하는 함수가 있다고 하자
// 두 함수의 코드 중복을 줄이기 위해 비상수 함수에서 상수 함수를 호출하는 방법을 사용한다
class TextBlock :
{
private:
string text;
public:
// 상수 반환 함수
const char& operator[](int position) const
{
...
return text[position];
}
// 비상수 반환 함수
char& operator[](int position)
{
...
// return text[position]
return const_cast<char&>( // 2. const char&로 반환된 값을 char&로 변환
static_cast<const TextBlock&>(*this)[position] // 1. *this 타입을 const로 변환
);
}
}
- static_cast를 사용한 이유는 비상수 함수에서 상수 함수를 호출하기 위함이다
- 이 과정을 거치지 않으면 비상수 함수는 재귀적으로 자기 스스로를 호출하게 될 것
- 상수 함수를 호출함으로써 추가된 const 키워드를 제거하기 위해 다시 const_cast를 사용
- 멤버 변수 text가 원래 const가 아니므로, 비상수 메서드를 통해 read/wrtie이 모두 가능
int i = 3; // i는 비상수
const int& rci = i; // rci는 상수 레퍼런스
const_cast<int&>(rci) = 4; // OK: modifies i (i가 비상수이기 때문에 정상 시행)
const int j = 3; // j는 상수
int* pj = const_cast<int*>(&j); // 비상수로 변환 시도
*pj = 4; // 원본이 상수인 상태에 write를 시도하므로, undefined behaviour 발생
dynamic_cast
- Inheritance Casting: Base <-> Derived
- 런타임에서 캐스팅
- 다형 형식의 파생 클래스 간의 캐스팅
- virtual method로 다형 형식의 계층이어야만 RTTI를 사용해 캐스팅 가능
- 캐스팅 실패 시 null을 리턴
- null 체크를 통해 안전성 확보 가능
- upcasting
- 파생 클래스 -> 부모 클래스로 캐스팅
- downcasting
- 부모 클래스 -> 파생 클래스로 캐스팅
// ex1
Parent* parent = new Parent; // 페런트 그 자체
Child* child = new Child; // 차일드 그 자체
Parent* parent2 = new Child; // 페런트의 탈을 쓴 차일드
Parent* upcasted = dynamic_cast<Parent*>(child);
Child* downcasted1 = dynamic_cast<Child*>(parent2);
// 캐스팅 실패 시 null 을 반환,
// 안전한 캐스팅 가능
Child* downcasted1 = dynamic_cast<Child*>(parent); // failed
// ex2
class A {};
class B : public A {};
int main()
{
A* b = new B;
// 아래 코드는 컴파일 불가
B* a = dynamic_cast<B*>(b);
return 0;
}
// 클래스 A와 B가 다형형식을 공유하지 않기 때문
// 아래와 같이만 변형해도 컴파일 가능
class A {
virtual ~A();
};
class B : public A {
~B();
};
How dynamic_cast works?
- polymorphic type에만 사용 가능한 캐스팅
- virtual method를 가지고 있거나,
- virtual destructor를 가지고 있거나,
- virtual base class를 가지고 있는 클래스는 polymorphic
- polymorphic 타입은 Virtual Method Table(VMT)를 데이터 레이아웃에 가진다
- polymorphic 타입이 아니면 VMT가 없다 (에러 발생)
- VMT에서 변환 가능한 타입을 찾아 캐스팅
다른 캐스팅 방법
// basically the same behavior
int(a); // Function
(int)a; // Operator
캐스팅 시도 순서*
- const_cast
- static_cast
- static_cast + const_cast
- reinterpret_cast
- reinterpret_cast + const_cast
출처
- https://en.cppreference.com/w/cpp/language/explicit_cast
- http://www.cplusplus.com/doc/tutorial/typecasting/
- https://stackoverflow.com/questions/13783312/how-does-dynamic-cast-work
- https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=wkdghcjf1234&logNo=220210906503
- https://en.cppreference.com/w/cpp/language/const_cast