[C++] 타입 변환 4: 포인터
타입 변환 4: 포인터
인프런 Rookiss님의 'Part1: C++ 프로그래밍 입문' 강의를 기반으로 정리한 필기입니다.
😎[C++과 언리얼로 만드는 MMORPG 게임 개발 시리즈] Part1: C++ 프로그래밍 입문 강의 들으러 가기!
공통 부분
#include <iostream>
using namespace std;
// 타입 변환 (포인터)
class Knight{
public:
int _hp = 0;
};
class Item{
public:
Item() { cout << "Item()" << endl; }
Item(int itemType) : _itemType(itemType) { }
Item(const Item& item) { cout << "Item(const Item&)" << endl; }
~Item() { cout << "~Item()" << endl; }
public:
int _itemType = 0;
int _itemDbld = 0;
char _cummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진 아이템 클래스를 나타내기 위해 표시
};
enum ItemType
{
IT_WEAPON = 1,
IT_ARMOR =2,
};
class Weapon : public Item
{
public:
Weapon() : Item(IT_WEAPON)
{
_itemType = 1;
cout << "Weapon()" << endl;
}
~Weapon()
{
cout << "~Weapon()" << endl;
}
public:
int _damage = 0;
};
class Armor : public Item{
public:
Armor() : Item(IT_ARMOR) { cout << "Armor()" << endl; }
~Armor() { cout << "~Armor()" << endl; }
public:
int _defence = 0;
};
연관성이 없는 클래스 사이의 포인터 변환 테스트
int main()
{
// Stack [ 주소 ] -> Heap [ _hp(4) ]
Knight* knight = new Knight();
// 암시적으로는 NO
// 명시적으로는 OK
// Stack [ 주소 ]
Item* item = (Item*)knight;
item->_itemType = 2; // 4byte로 유효한 정보를 건드리기 때문에 정상적으로 변경된다.
item->_itemDbld = 1; // 데이터의 유효 범위를 벋어나서 문제가 된다.
delete knight;
}
delete knight; 하지 않는 경우, 컴파일은 통과되지만 item->_itemDbld = 1;가 문제가 된 상태로 넘어갈 수 있다.
이 경우, 추후에 나비효과가 되어 돌아올 수 있다.
다운 캐스트: 부모 -> 자식 변환 (Base class -> Derived class)
부모 -> 자식 변환은 명시적으로 사용해야 통과된다.
int main()
{
Item* item = new Item();
// [ [ Item ] ]
// [ _damage ]
Weapon* weapon = (Weapon*)item; // 위험한 상황, 잘못된 코드
Weapon->_damage = 10; // 잘못된 메모리를 건드림, 잘못된 코드
delete item;
}
Object Slicing 을 조심해야 한다.
메모리의 크기가 큰 것에서 작은 것으로 변환하는 경우, 엉뚱한 메모리를 건드릴 수 있다.
위의 경우, item은 weapon보다 메모리의 크기가 크다. (*Weapon)item과 같이 명시적 변화를 하면 엉뚱한 메모리를 건드릴 수 있다.
명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!
자식 -> 부모 변환 테스트
int main()
{
// [ [ Item ] ]
// [ _damage ]
Weapon* weapon = new Weapon();
// 암시적으로도 된다!
Item* item = weapon; // Item* item = (item*) weapon; // (item*) 부분이 생략되어있다
delete weapon;
}
조심할 사항들
명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!
암시적으로 될 때는 안전하다?
-> 평생 명시적으로 타입 변환(캐스팅)은 안 하면 되는거 아닌가?
int main()
{
Item* inventory[20] = {};
srand((unsigned int)time(nullptr));
for (int i = 0; i < 20; i++)
{
int randValue = rand() % 2; // 0~1
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 == IT_WEAPON)
{
Weapon* weapon = (Weapon*)item;
cout << "Weapon Damage : " << weapon->_damage << endl;
}
}
}
실행화면
int main()
{
for (int i = 0; i < 20; i++)
{
Item* item = inventory[i];
if (item == nullptr)
continue;
//** Virtual 함수 변경으로 delete item; 하나로 해결
if (item->_itemType == IT_WEAPON)
{
Weapon* weapon = (Weapon*)item;
delete weapon;
}
else
{
Armor* armor = (Armor*)item;
delete armor;
}
delete item;
}
// [결론]
// - 포인터 vs 일반 타입 : 차이를 이해하자
// - 포인터 사이의 타입 변환(캐스팅)을 할 때는 매우 매우 조심해야 한다!
// - 부모-자식 관계에서 부모 클래스의 소멸자에는 까먹지 말고 virtual을 붙이자!!! // 코딩 테스트와 면접 단골문제!
return 0;
}
포인터 vs 일반 타입 : 차이를 이해하자
- 포인터 사이의 타입 변환(캐스팅)을 할 때는 매우 매우 조심해야 한다!
- 부모-자식 관계에서 부모 클래스의 소멸자에는 까먹지 말고 virtual을 붙이자!!!
전체 코드
더보기
#include <iostream>
using namespace std;
// 타입 변환 (포인터)
class Knight
{
public:
int _hp = 0;
};
class Item
{
public:
Item()
{
cout << "Item()" << endl;
}
Item(int itemType) : _itemType(itemType)
{
}
Item(const Item& item)
{
cout << "Item(const Item&)" << endl;
}
~Item()
{
cout << "~Item()" << endl;
}
public:
int _itemType = 0;
int _itemDbld = 0;
char _cummy[4096] = {}; // 이런 저런 정보들로 인해 비대해진 아이템 클래스를 나타내기 위해 표시
};
enum ItemType
{
IT_WEAPON = 1,
IT_ARMOR =2,
};
class Weapon : public Item
{
public:
Weapon() : Item(IT_WEAPON)
{
_itemType = 1;
cout << "Weapon()" << endl;
}
~Weapon()
{
cout << "~Weapon()" << endl;
}
public:
int _damage = 0;
};
class Armor : public Item
{
public:
Armor() : Item(IT_ARMOR)
{
cout << "Armor()" << endl;
}
~Armor()
{
cout << "~Armor()" << endl;
}
public:
int _defence = 0;
};
int main()
{
// 연관성이 없는 클래스 사이의 포인터 변환 테스트
{
// Stack [ 주소 ] -> Heap [ _hp(4) ]
Knight* knight = new Knight();
// 암시적으로는 NO
// 명시적으로는 OK
// Stack [ 주소 ]
// tem* item = (Item*)knight;
// item->_itemType = 2;
// item->_itemDbld = 1;
delete knight;
}
// 부모 -> 자식 변환 테스트
{
Item* item = new Item();
// [ [ Item ] ]
// [ _damage ]
Weapon* weapon = (Weapon*)item; // 위험한 상황, 잘못된 코드
Weapon->_damage = 10; // 잘못된 메모리를 건드림, 잘못된 코드
delete item;
}
// 자식 -> 부모 변환 테스트
{
// [ [ Item ] ]
// [ _damage ]
Weapon* weapon = new Weapon();
// 암시적으로도 된다!
Item* item = weapon; // Item* item = (item*) weapon; // (item*) 부분이 생략되어있다
delete weapon;
}
// 명시적으로 타입 변환할 때는 항상 항상 조심해야 한다!
// 암시적으로 될 때는 안전하다?
// -> 평생 명시적으로 타입 변환(캐스팅)은 안 하면 되는거 아닌가?
Item* inventory[20] = {};
srand((unsigned int)time(nullptr));
for (int i = 0; i < 20; i++)
{
int randValue = rand() % 2; // 0~1
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 == IT_WEAPON)
{
Weapon* weapon = (Weapon*)item;
cout << "Weapon Damage : " << weapon->_damage << endl;
}
}
return 0;
}
'⭐ Programming > C++' 카테고리의 다른 글
[C++] 얕은 복사 vs 깊은 복사 2 (0) | 2022.04.10 |
---|---|
[C++] 얕은 복사 vs 깊은 복사 1 (0) | 2022.04.09 |
[C++] 타입 변환 3: 포인터 타입 변환 (0) | 2022.04.08 |
[C++] 타입 변환 2: 참조 타입 변환 (0) | 2022.04.08 |
[C++] 타입 변환 1: 값, 타입 변환 / 암시적, 명시적 변환 (0) | 2022.04.08 |
댓글
이 글 공유하기
다른 글
-
[C++] 얕은 복사 vs 깊은 복사 2
[C++] 얕은 복사 vs 깊은 복사 2
2022.04.10 -
[C++] 얕은 복사 vs 깊은 복사 1
[C++] 얕은 복사 vs 깊은 복사 1
2022.04.09 -
[C++] 타입 변환 3: 포인터 타입 변환
[C++] 타입 변환 3: 포인터 타입 변환
2022.04.08 -
[C++] 타입 변환 2: 참조 타입 변환
[C++] 타입 변환 2: 참조 타입 변환
2022.04.08