동적 메모리 할당과 해제,댕글링 포인터,vector의 내부동작
가상 함수 글에서 보면
Shop 있고 그 샵에서 CreateItem 함수를 이용해서 여러 종류의 아이템을 만들 수 있다.
class NameBase
{
private:
std::string m_Name;
public:
std::string Name()
{
return m_Name;
}
void Name(const std::string& _Name)
{
m_Name = _Name;
}
};
class Item : public NameBase
{
public:
Item(std::string _Name, int _ItemType = 0) : m_Type(_ItemType)
{
Name(_Name);
}
public:
virtual void Render()
{
std::cout << Name() << std::endl;
}
};
class Weapon : public Item
{
private:
int m_Att;
public:
void Render()
{
Item::Render();
std::cout << "공격력 " << m_Att << std::endl;
}
public:
Weapon(std::string _Name) : Item(_Name, 1), m_Att(1)
{
}
};
class Shop
{
public:
std::vector<Item*> ItemInventory;
public:
template<typename T>
void CreateItem(std::string _Name)
{
ItemInventory.push_back(new T(_Name));
}
void DeleteItem(int _Index)
{
delete ItemInventort[_Index];
}
public:
void Render()
{
for (size_t i = 0; i < ItemInventory.size(); i++)
{
if (nullptr == ItemInventory[i])
{
continue;
}
std::cout << i << ". " << ItemInventory[i]->Name() << std::endl;
}
}
};
int main()
{
Shop NewShop = Shop();
NewShop.CreateItem<Weapon>("철검");
NewShop.CreateItem<Armor>("방패");
NewShop.CreateItem<Potion>("HP포션");
}
이런식의 코드이며 Armor클래스와 Potion 클래스도 있다고 가정하고
Shop에서 3종류의 아이템을 생성했다.여기서 new를 이용하여 동적할당을 하게된다.
메모리 적으로 본다면
이렇게 볼 수 있다.CreateItem을 하면 Item*가 만들어지고 즉 ItemIventory 벡터 안에는
Item*가 들어있고 이게 철검,방패,HP포션과 연결되어 있는것이다.(철검,방패 등 아이템을
들고있는게 아니다.)
DeleteItem 함수를 이용해서 0번째에 있는 철검을 삭제했다고 가정해보자.
그러면 이런 상황이 된다.그림에서 보듯이 철검은 삭제됬는데 철검을 가리키고 있던
Item*는 계속 그쪽을 가리키고 있다. 이런 경우를 댕글링 포인터라고 하고 굉장히 위험한 코드이다.
void DeleteItem(int _Index)
{
delete ItemInventory[_Index];
ItemInventory[_Index] = nullptr;
}
그렇기 때문에 이렇게 연결자체도 끊어줘야 한다. 그런후에 상점에서 0번 아이템인 철검이 사라졌으니
0번에는 방패가 1번에는 Hp포션 아이템이 이런식으로 하나씩 내려와야 한다고 가정하면
for (size_t i = _Index; i < ItemInventory.size() - 1; i++)
{
ItemInventory[i] = ItemInventory[i+1];
}
이렇게 만들 수 있다. 메모리적으로 본다면
이런 상황이 된것이다.이상황에서 Shop의 Render를 호출하면
0.방패
1.HP포션
2.HP포션
이렇게 출력될것이다.그렇기 때문에
ItemInventory[ItemInventory.size() - 1] = nullptr;
맨 마지막은 항상 잘못된 복사된 값이 하나 더 존재하기 때문에 맨 마지막의 연결도 끊어줘야 한다.
그러면 이제 원하던 결과로 출력이 된다.
std::vector<Item*> ItemInventory
여기서 보면 자료구조의 하나인 vector를 사용해서 ItemInventory를 만들어서 Item을 관리하고있다.
vector의 내부를 생각해보면
template<typename Data>
class vector
{
private:
Data* m_pData;
int m_Size;
public:
vector(int _Count) : m_Size(_Count), m_pData(nullptr)
{
m_pData = new Data[m_Size];
}
};
벡터는 동적 배열구조 클래스이며 그렇기 때문에 new를 사용해서 넣어준 Size만큼 동적할당을 한다.
(실제 vector는 size와 capacity 값을 가지고 실제 자료의 개수(size)와 벡터가 가질수 있는
최대 개수(capacity) 값을 비교해서 공간이 부족하면 공간을 늘려서 새로할당받는 동작을
거친다.)
이렇게 보면 여기서 사용한 vector<Item*> 이 코드는 벡터의 내부로 들어가면
Data*형에 들어가기 때문에 결과적으로 Item**가 된다.그렇기 때문에
실제 메모리의 형태는 이러한 형태가 된다.