static_cast
adynamic_cast
const_cast
reinterpret_cast
이 4가지로 다 캐스팅한다
(int)이것은 C에서 넘어온 고전적인 캐스팅 방식
C++로 작업을 할 때는 이런 (int) c스타일은 자제하고
4가지중 하나를 선택해서 캐스팅 해야한다.
타입 원칙에 비춰볼 때 상식적인 캐스팅만 허용해준다.
단, 안정성은 보장 못함.
1) int <-> float
ex) 3 -> 3.0f, 3.5 -> 3 이런거
2) 상속관계의 클래스 경우
Player -> Knight (다운캐스팅)
int hp = 100;
int maxHp = 200;
float ratio = hp / maxHp; => 정수 / 정수 라 0이 나옴.
주의!
그래서 둘중 하나를
float ratio = (float)hp / maxHp;
이렇게 변환해줬음. float 가 우선순위가 높기 대문에
실수 / 실수 => 실수 나옴.
근데 이것은 C스타일이고 C++로하자면은
float ratio = static_cast<float>(hp) / maxHp;
이렇게된다.
Player는 Player이지 Knight가 아니기 때문에
p를 Knight로 변환한다는 것 자체가 위험하다.
현재는 원본은 Knight이고 들고있는거만 Player포인터 이다.
이럴경우에는 그냥 캐스팅을 해도 안전하기는 함.
이럴경우 사용할 수 있는 캐스팅이
이렇게 (Knight*)로 캐스팅해줌.
자식 -> 부모의 경우 아무 문제가 없다
(아무 문제가 없다라고 하기보다는 부모의 포인터로 변환했으니까 크기가 더 작은 걸로 변환했으니까 -> Knight의 멤버 변수에 접근하면 위험함. 안되는것은 아니지만 엉뚱한 값을 건드릴 위험이 있다.)
그래서
이렇게 가능한게
static_cast<Player*>(k); 이렇게 대체가 가능함.
부모 -> 자식으로 다운 캐스팅한것을 명시적으로 해줄 수 있음.
아까 위에서 안정성은 보장못한다는 말이 뭐냐하면은
knight와 Archer의 설계가 완전히 달라서(크기도 다르고)
k1->에 접근해서 이상한 값을 수정할 수 도 있는 것이다.
엉뚱한 메모리를 고치는 상황 발생.
static_cast의 단점을 조금 보완해줌.
상속 관계에서의 안전 형변환.
dynamic_cast는 "RTTI"를 사용한다.
"RunTime Type Informatiom" => "다형성"을 활용하는 방식.
"다형성" => virtual 함수를 부모 클래스에 만들때 -> .vftable 생성이 되는데 객체의 맨앞에 주소에 표짓말 처럼 박힌다.
이것을 유식하게 "RTTI"라고 부른다.
"실시간으로 코드가 동작할 대 타입을 알 수 있는 것"
따라서 dynamic_cast를 사용하고싶다면 virtual함수가 반드시 하나라도 등장을 해야한다.
k2라는 실제 객체가 무엇인지 가상함수 테이블을 참조를 하여 나태날 수 있다.
virtual함수를 하나라도 만들면, 객체의 실제 메모리에 가상함수 테이블(vftable) 주소가 기입이 된다.
이것을 이용해서 실제로 "캐스팅"(dynamic_cast)할 때,
이렇게 무작정 캐스팅을 하지않고
맞는 타입인지 체크를 한뒤에 캐스팅을 해준다는 차이점이 있다.
만약, 잘못된 타입으로 캐스팅을 했다면, nullptr로 반환을 한다 ❗❗
=> 먼말?
만약 원본이 Archer이고 이것을 업캐스팅을 통해서 Player* p가 들고있다고 하자.
이상태에서 Knight k1 = static_cast<Knight>(p); 를 해주게 되면
다른 메모리를 오염시키는 문제가 발생한다. (k1-> 값 수정을 한다면)
반면 dynamic_cast의 경우 RTTI를 사용해서
Knight k2 = dynamic_cast<Knight>(p);
p가 Knight인지 아닌지를 가상함수 테이블을 통해 체크를 해서
맞다면 캐스팅을 해주고 아니라면 nullptr을 반환한다는 것이다.
동적할당으로 Knight객체 3개를 만들었을때 당연히 메모리에 올라가는 주소는 각기 다 다르고
Player의 소멸자 부분에 virtual을 붙여주었기 때문에
메모리에 올라갈때 vftable을 달고 올라간다.
int _hp = a;이다 (10)
밑줄친 부분이 가상함수 테아블 주소인데
이게 k3, k4, k5다 똑같다.
이것으로 Player의 소멸자인지 Knight의 소멸자인지를 구분하여 호출하는 것이다.
를 사용하여 맞는 타입으로 캐스팅했는지 확인하는데 유용하다.
그런데 상속 관계에서는 그러면 dynamic_cast가 안전하고 static_cast는 위험이 조금있으니까
다이나믹만 써야지? => 이건 또 안됨.
dynamic_cast의 경우 당연히 static_cast보다 조금더 느릭 동작할 수 빆에 없다. (원본을 체크를 하니까)
타입 변환 3 : 포인터 에서
#include <iostream>
using namespace std;
// 타입 변환 3 : 포인터
class Knight
{
public:
int _hp = 0;
};
class Item
{
public:
Item()
{
cout << "Item 기본 생성자 호출!" << endl;
}
Item(int itemType)
:
_itemType(itemType)
{
cout << "Item itemType 생성자 호출!" << endl;
}
Item(const Item& item)
{
cout << "Item 복사 생성자 호출!" << endl;
}
virtual ~Item()
{
cout << "Item 소멸자 호출!" << endl;
}
public:
int _itemType = 3;
int _itemDbid = 3;
char _dummy[4096] = {};
};
enum ItemType
{
IT_Weapon = 1,
IT_Armor = 2,
};
class Weapon : public Item
{
public:
Weapon()
:
Item(IT_Weapon)
{
cout << "Weapon 생성자 호출!" << endl;
}
virtual ~Weapon()
{
cout << "Weapon 소멸자 호출!" << endl;
}
public:
int _damage = 10;
};
class Armor : public Item
{
public:
Armor()
:
Item(IT_Armor)
{
cout << "Armor 생성자 호출!" << endl;
}
virtual ~Armor()
{
cout << "Armor 소멸자 호출!" << endl;
}
public :
int _defense = 10;
};
int main()
{
Item* inventory[20] = {};
srand((unsigned)time(nullptr));
for (int i = 0; i < 20; ++i)
{
int result = rand() % 2; // 0~1
switch (result)
{
case 0:
inventory[i] = new Weapon();
break;
case 1:
inventory[i] = new Armor();
break;
default:
cout << "Error!" << endl;
break;
}
}
cout << sizeof(Item) << endl;
cout << sizeof(Weapon) << endl;
for (int i = 0; i < 20; ++i)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
if (item->_itemType == IT_Weapon)
{
Weapon* weapon = (Weapon*)item;
cout << weapon->_damage << endl;
}
}
for (int i = 0; i < 20; ++i)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
delete item;
}
return 0;
}
이런식으로 Item을 상속받는 Weapon에 기본생성자의
선처리 영역에서 인자를 하나만 받는 Item 생성자를 호출하여 ItemType을 지정하고나서
if문 안에서 ItemType에 따라서
Weapon weapon = (Weapon)itme;이렇게 캐스팅 해주었는데 이부분을 이제
for (int i = 0; i < 20; ++i)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
if (item->_itemType == IT_Weapon)
{
Weapon* weapon = static_cast<Weapon*>(item);
cout << weapon->_damage << endl;
}
}
이렇게 바꾸면 조금 더 빠를 수 있다는 것이다.
const를 붙이거나 때거나~
누군가가 이런식으로 함수를 만들어서 이것을 수정할 수 없고 그대로 가져다가 써야하는 상황일 경우
"Rookiss"라는 문자열의 데이터 타입은
const char* 이다 보니까 인자로 넘겨줄 수 없다.
그래서 const를 빼기 위해서 (char*)로 캐스팅가능하지만
실수할 여지가 있기 때문에
const_cast<char*>("Rookiss")
이렇게 넘겨줄 수 있다.
하지만 거의 대부분의 경우에서 이것을 사용할 경우는 진짜 없다.
면접을 위해서 공부를 하자.
가장 위험하고 강력한 형태의 캐스팅.
re-interpret : 다시-간주하다/생각하다.
포인터랑 전혀 관계없는 다른 타입 변환 등
이렇게 C스타일로 형변환 가능하지만
전혀 엉뚱한 값으로 캐스팅 할 때 사용이 가능하다.
포인터 <-> 정수 가능함.
또한
Dog라는 아무런 연관성이 없는 클래스들 끼리도 변환이 가능하다.
Dog*에다가 k2를 넣어주면 당연히 말이 안됨.
static_cast를 사용하더라도 통과가 안된다.
static_cast, dynamic_cast는 정말로 확실하게 부모-자식 상관관계 있을 때나 사용할 수 있지(static_cast는 값 캐스팅 됨)
언제 어디서나 막 사용할 수 있는 문법은 아니다.
그래서 이런 생뚱맞은 변환을 하고 싶을 경우
이렇게 reinterpret_cast 사용한다.
그러면 시발 이럴 경우가 없는데 왜 사용을 하냐?
=> C의 경우 malloc같은 경우에 사용할 수 있다.
malloc의 반환 타입이 void* 인데 이것을 우리가
원하는 형태로 캐스팅 해주어야한다.
void* p = malloc(1000);
// 어떤 의미로 사용할지는 모르겠으니까 니가 알아서 사용해줘 이느낌 이였다.
Dog* dog = p; // 기본적으로 통과가 안된다.
// 이럴때 C스타일의 캐스팅을 사용해서
Dog* dog = (Dog*)p; // 이렇게 형변환 해주었음. ( 실수 할 여지 있음)
// 근데 이제는 C++ 문법을 사용해야한다
Dog* dog = reinterpret_cast<Dog*>(p);
// 이렇게 사용가능함.
90% -> static_cast
8% -> dynamic_cast
1% -> const_cast
1% -> reinterpret_cast
활용빈도가 이럼.