타입 변환 (포인터, 연관 없음)
#include <iostream>
using namespace std;
class Knight
{
public:
int _hp = 10;
};
class Item
{
public:
Item()
{
cout << "Item()" << endl;
}
Item(const Item& itme)
{
cout << "Item(const Item&)" << endl;
}
~Item()
{
cout << "~Item()" << endl;
}
public:
int _itemType = 0;
int _itemDbld = 0;
char _dummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진
};
int main()
{
// 연관성이 없는 클래스 사이의 포인터 변환 테스트
{
// 스택에 kngith라고 하는 바구니에 주소가 담겨 있는데
// 그 주소는 힙 영역에 있음
Knight* kngiht = new Knight();
// 암시적으로는 no
// 명시적으로는 ok
// 스택에 아이템이라는 바구니에 주소가 담겨 있는데
// 그 주소를 타고가면 아이템이 라는 타입이 있다고 주장한 격
Item* item = (Item*)kngiht;
item->_itemType = 2; // 문제없음 정말 우연
item->_itemDbld = 1; // 역시 문제 없음 하지만 이상한 메모리를 수정한 격
delete kngiht; // 이걸 해주면 그나마 컴파일러가 힙 메모리가 잘못됐다고 잡아줌
}
return 0;
}
이런 코드라면 문제가 많이 발생할 것이다. 꼭 주의해서 사용하도록 하자. 물론 저렇게 타입을 변환하는 경우는 100만번 중 한번 있을까 말까한 변환이지만 실수를 한번하면 치명적이니 꼭 알아두자.
다음은 자식과 부모, 부모와 자식 관계에서 포인터 변환이다.
타입 변환(포인터, 상속 관계)
#include <iostream>
using namespace std;
class Knight
{
public:
int _hp = 10;
};
class Item
{
public:
Item()
{
cout << "Item()" << endl;
}
Item(int itemType) :_itemType(itemType)
{
cout << "asd";
}
Item(const Item& itme)
{
cout << "Item(const Item&)" << endl;
}
virtual ~Item()
{
cout << "~Item()" << endl;
}
public:
int _itemType = 0;
int _itemDbld = 0;
char _dummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진
};
class Weapon : public Item
{
public:
Weapon() : Item(1)
{
cout << "Weapon()" << endl;
}
~Weapon()
{
cout << "~Weapon()" << endl;
}
int _damage = 1;
};
class Armor : public Item
{
public:
Armor() : Item(2)
{
cout << "Armor()" << endl;
}
~Armor()
{
cout << "~Armor()" << endl;
}
};
int main()
{
// 부모 -> 자식
{
Item* item = new Item();
// 항상 아이템은 웨폰이 아니기 때문에 보장이 안됨
// 따라서 명시적으로
Weapon* weapon = (Weapon*)item;
//메모리 범위를 벗어나서 수정 했기 때문에 오류
weapon->_damage = 10;
delete item;
}
// 자식 -> 부모
{
Weapon* weapon = new Weapon();
//암시적으로도 된다.
Item* item = weapon;
delete weapon;
}
// 명시적으로 타입 변환할 때는 항상 조심해야한다!!
// 암시적으로 될 때는 안전하다??
// - 평생 명시적으로 타입 변환을 안하면 되는거 아닌가??
Item* inventory[20]{};
for (int i = 0; i < 20; i++)
{
int randValue = rand() % 2;
switch (randValue)
{
case 0:
inventory[i] = new Weapon();
break;
case 1:
inventory[i] = new Armor();
break;
}
}
for (int i = 0; i < 20; i++)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
if (item->_itemType == 1)
{
// 위에서 Weapon으로 선언 했기 때문에
// 이렇게 웨폰으로 변환하는게 100퍼 안전하다
// 이런식으로 변환을 할 수 도 있기 때문에
// 명시적으로 하는걸 안쓸 수는 없다.
Weapon* weapon = (Weapon*)item;
cout << "weapon->_damage " << weapon->_damage << endl;
}
}
//*********** 매우 매우 중요************//
for (int i = 0; i < 20; i++)
{
// 원본은 아머 또는 웨폰이었음
Item* item = inventory[i];
if (item == nullptr)
continue;
delete item;
}
// 위의 코드를 실행하면 item의 소멸자만 실행됨 즉 웨폰의 소멸자가 실행이 안되기 때문에
// 조심 해야함.
// 그래서 소멸자에서 어떤 처리를 해줘야하는데 그 부분이 생략하게 됨
// 물론 메모리 누수는 없음.
// 따라서 다음 처럼 해야함
for (int i = 0; i < 20; i++)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
if (item->_itemType == 1)
{
Weapon* weapon = (Weapon*)item;
delete weapon;
}
if (item->_itemType == 2)
{
Armor* armor = (Armor*)item;
delete armor;
}
}
// 그런데 이렇게 하게되면 되게 불편하고 item을 상속 받는 class가 많으면 많을 수록 코드가 길어짐
// 해결 방안은 Item의 소멸자를 가상함수로 만들어 주게되면 해결완료
// 만약 소멸자가 가상함수로 바뀐다면 첫번째 방식을 사용해도 됨
for (int i = 0; i < 20; i++)
{
// 원본은 아머 또는 웨폰이었음
Item* item = inventory[i];
if (item == nullptr)
continue;
delete item;
}
return 0;
}
'C++ > 기초' 카테고리의 다른 글
[C++] 전방 선언 (0) | 2023.11.29 |
---|---|
[C++] 복사, cast (0) | 2023.11.28 |
[C++] 타입 변환 - 1 (0) | 2023.11.28 |
[C++] 동적 할당 (1) | 2023.11.28 |
[C++] 객체 지향 - 3 (0) | 2023.11.27 |