[C++] 캐스팅: static, dynamic, const, reinterpret cast
캐스팅 4총사 (static, dynamic, const, reinterpret cast)
인프런 Rookiss님의 'Part1: C++ 프로그래밍 입문' 강의를 기반으로 정리한 필기입니다.
😎[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 강의 들으러 가기!
cast의 종류 : static cast, dynamic cast, const cast, reinterpret cast
static cast
- static cast는 컴파일 타임에 진행된다.
- 타입 변환에 자주 사용. 다운 캐스팅, 업 캐스팅 시 사용 가능.
- Cast 시 안전 검사를 하지 않는다.
dynamic cast
- RTTI를 기반으로 런타임에 적용한다.
- cast로 클래스 간 Up, Down cast에 자주 사용되며 cast 시 안전 검사를 한다.
const cast
- 변수에 const 속성을 등록 및 해제하기 위해 사용하는 cast다. (= const를 붙이거나 떼거나 할 때 사용한다.)
reinterpret cast
- 포인터 간 변환을 위해 사용하는 cast다.
- 임의의 포인터 객체를 다른 포인터 객체로 변환할 수 있다. 이러한 변환은 안전하지 않을 수 있기 때문에 주의가 필요하다.
※ C++에서는 가상 함수(virtual function)을 사용하여 런타임 다형성을 구현할 수 있으므로 업캐스팅(Up Casting) 없이도 객체 타입에 따라 올바른 함수가 호출될 수 있다. 따라서 cast를 명시적으로 사용하는것 보다 가상 함수를 사용하는 것이 더 좋다.
1. static _cast
static_cast : 타입 원칙에 비춰볼 때 상식적인 캐스팅만 허용해준다. 4가지 cast 방식 중 가장 많이 활용한다.
static_cast 사용 경우: 1. 타입 변환, 2. 다운캐스팅(Down Casting), 3. 업캐스팅(Up Casting)
1) 타입 변환
- int <-> float
2) 다운캐스팅(Down Casting)
- 다운캐스팅은 '부모클래스->자식클래스', 단, 안전성 보장 못함
- Player* -> Knight*
3) 업캐스팅(Up Casting)
- 잘 사용하지 않음.
- '자식->부모' 이기 때문에 굳이 캐스팅을 하지 않아도 문제가 되지 않는 경우가 대부분이다.
#include <iostream>
using namespace std;
class Player { };
class Knight : public Player { };
class Archer : public Player { };
int main()
{
int hp = 100;
int maxHp = 200;
// float ratio = hp / maxHp // 인수 나눗셈이어서 0.5가 아닌 0으로 출력된다
// float ratio = (float)hp / maxHp; // float에서 int를 나누면 float가 우선순위가 높아서 실수로 나온다
float ratio = static_cast<float>(hp) / maxHp; // 다운캐스팅. C++ 친화적인 문법
// 자식->부모 : 굳이 캐스팅이 필요가 없다
Knight* k = new Knight();
Player* p2 = k; // 캐스팅을 하지 않아도 문제가 되지 않는다
// 부모->자식
Player* p = new Knight();
// Knight* k1 = (Knight*)p; // (Knight*)p는 c스타일 타입변환
Knight* k1 = static_cast<Knight*>(p); // 좀 더 정확한 의미
// 부모->자식, 다운캐스팅이 문제되는 상황
Player* p = new Archer();
Knight* k1 = static_cast<Knight*>(p);
k1-> //포인터로 접근해서 값을 고치면 엉뚱한 메모리를 고치는 상황이 발생할 수 있다.
return 0;
}
2. dynamic_cast
dynamic_cast : 상속 관계에서의 안전 형변환
RTTI (RunTime Type Information)
- 다형성을 활용하는 방식
- 런타임 중 타입을 확인할 수 있다.
dynamic_cast를 사용하려면 virtual 함수가 필요하다.
- virtual 함수를 하나라도 만들면, 객체의 메모리에 가상 함수 테이블 (vftable) 주소가 기입된다.
- 만약 잘못된 타입으로 캐스팅을 했으면, nullptr 반환한다. dynamic_cast의 경우 RTTI의 가상 함수 테이블(vftable)를 활용하여 자신이 어떤 애인지 확인하기 때문에 잘못된 타입으로 변환하면, nullptr로 반환한다. (반면에 static_cast는 메모리오염이 발생한다.)
- 이를 이용해서 맞는 타입으로 캐스팅을 했는지 확인을 유용하다.
- static_cast보다 느리다.
↑ virtual 함수가 하나도 없으면 위의 이미지와 같은 에러 문구를 띄우며 빌드가 되지 않는다.
#include <iostream>
using namespace std;
class Player
{
public:
virtual ~Player() { }
};
class Knight : public Player { };
class Archer : public Player { };
class Dog { };
int main()
{
Player* p = new Knight();
Knight* k2 = dynamic_cast<Knight*>(p);
return 0;
}
nullptr 반환 예시 코드
#include <iostream>
using namespace std;
class Player
{
public:
virtual ~Player() { }
};
class Knight : public Player { };
class Archer : public Player { };
class Dog { };
int main()
{
Player* p = new Archer();
Knight* k2 = dynamic_cast<Knight*>(p); // 잘못된 타입으로 캐스팅한 경우. nullptr 반환
return 0;
}
3. const_cast
const_cast : const를 붙이거나 떼거나 할 때 사용한다.
사용 예시: 공동 작업을 할 때 누군가 const로 값 변화를 막아두었을때, 값을 변화시켜 테스팅하고 싶을 때 사용한다.
#include <iostream>
using namespace std;
void PrintName(char* str)
{
cout << str << endl;
}
int main()
{
// 공동 작업 시 PrintName 함수를 수정하면 안 되는 상황
PrintName("Rookiss"); // const char* 형이기 때문에 통과 안됨
PrintName((char*)"Rookiss") // c스타일의 타입변환. 통과는 되지만 추천하지 않는 방식
PrintName(const_cast<char*>("Say my name")); // c++ 스타일. const_cast
return 0;
}
4. reinterpret_cast
reinterpret_cast는 포인터랑 전혀 관계없는 다른 타입 변환 등에 사용한다.
- 가장 위험하고 강력한 형태의 캐스팅
- 're-interpret' : 다시-간주하다/생각하다.
- 사용 예시:
- void* p = malloc(1000);
- 해당 경우에 malloc은 void*로 반환한다.
- Dog* dog2 = reinterpret_cast<Dog*>(p);로 써서 void*형을 사용하여 Dog로 만들어주겠다.
#include <iostream>
using namespace std;
class Player
{
public:
virtual ~Player() { }
};
class Knight : public Player { };
class Archer : public Player { };
class Dog { };
int main()
{
Knight* k2 = dynamic_cast<Knight*>(p);
__int64 address = (__int64)(k2); // c스타일 캐스팅
__int64 address = reinterpret_cast<__int64>(k2); // c++ reinterpret_cast
Dog* dog1 = reinterpret_cast<Dog*>(k2); // Knight와 Dog는 상관관계가 없지만 reinterpret_cast 할 수 있다.
void* p = malloc(1000); // void*형으로 1000byte를 할당하겠다.
// 할당한 메모리를 Dog* 형으로 사용하겠다.
Dog* dog2 = p; // 통과 안됨
Dog* dog2 = (Dog*)p; // c스타일 캐스팅. (Dog*)는 사용하는 경우가 많아 실수의 여지가 높다.
Dog* dog2 = reinterpret_cast<Dog*>(p);
return 0;
}
언리얼 cast
'⭐ Programming > C++' 카테고리의 다른 글
[C++] 함수 포인터 1 (0) | 2022.04.15 |
---|---|
[C++] 전방선언 (0) | 2022.04.12 |
[C++] 얕은 복사 vs 깊은 복사 2 (0) | 2022.04.10 |
[C++] 얕은 복사 vs 깊은 복사 1 (0) | 2022.04.09 |
[C++] 타입 변환 4: 포인터 (0) | 2022.04.09 |
댓글
이 글 공유하기
다른 글
-
[C++] 함수 포인터 1
[C++] 함수 포인터 1
2022.04.15 -
[C++] 전방선언
[C++] 전방선언
2022.04.12 -
[C++] 얕은 복사 vs 깊은 복사 2
[C++] 얕은 복사 vs 깊은 복사 2
2022.04.10 -
[C++] 얕은 복사 vs 깊은 복사 1
[C++] 얕은 복사 vs 깊은 복사 1
2022.04.09