https://zerosik00.tistory.com/57
C++ 인벤토리, 제작시스템 구현해보기 2
https://zerosik00.tistory.com/56 C++ 인벤토리, 제작시스템 구현해보기 1목표객체 지향 설계방식과 SOLID원칙을 기반으로 하여.C++ CLI환경에서 간단한 게임 시스템을 구현해보기.그중에서도 아이템과 제작
zerosik00.tistory.com
저번에 만든 클래스들의 기능을 구현해보았다.
우선 메인함수에서 이 클래스들을 이용해 만들 기능들은
- 아이템과 레시피 추가 및 조회
- 아이템 제작
- 아이템 검색(아이디, 이름을 통한 검색이나 레시피 등
관심이있다면 구현부는 글 마지막에 github링크를 참고바란다.
기능구현을 하다보니 이전 설계로는 부족한것들이 많았고, c++에 사용에 대한 미숙으로 const나 값전달방식, 포인터 사용등에 이슈가 꽤나 많았는데, 이부분들이 꽤 개선되었다.
하나씩 살펴보자면
//Inventory.h
#pragma once
#include <map>
#include "ItemDatabase.h"
class Inventory {
private:
int maxStack = 999;
std::map<int, int> stock;
public:
Inventory(){}
bool addItem(int itemId, int count);
bool consumeItem(int itemID, int count);
bool enoughItem(int itemID, int count);
const std::map<int, int>& getStock() const;
};
인벤토리의 getCount 함수를 삭제했다. 조회는 getStock으로 충분하고,
개수가 충분한지 확인하는것은 enoughItem함수가 동일한 기능을 한다.
두번째는 ItemDatabase.h
//ItemDatabase.h
#pragma once
#include <map>
#include "Item.h"
class ItemDatabase
{
private:
std::map<int, Item> itemDatabase;
static ItemDatabase* instance;
void InitializeItemDatabase();
ItemDatabase() { InitializeItemDatabase(); }
public:
ItemDatabase(const ItemDatabase&) = delete;
ItemDatabase& operator=(const ItemDatabase&) = delete;
static ItemDatabase* getInstance() {
if (instance == nullptr) {
instance = new ItemDatabase();
}
return instance;
}
std::map<int, Item> getAllItemInfo();
// 추가되거나 이미 존재하는 아이템의 id를 반환.
int tryAddCustomItem(const std::string& name, const std::string& desc);
const Item* getItemById(const int id);
const bool isItemExistById(const int id);
const int isItemExistByName(const std::string& name);
};
사용자는 일반적으로 id가 아닌 아이템의 이름으로 접근하게 될것이다.
이점을 망각하고 이전에는 id를 이용한 조회밖에 없었는데 이름을 통한 조회가 추가되었다.
그리고 임시 기능으로 tryAddCustomItem 함수를 추가하여 사용자가 아이템을 추가할 수 있도록 지원한다.
마지막은 CraftingManager.h
//CraftingManager.h
#pragma once
#include "ItemDatabase.h"
#include "CraftRecipe.h"
#include "Inventory.h"
#include <map>
#include <vector>
class CraftingManager
{
private:
std::vector<CraftRecipe> craftRecipes;
ItemDatabase* db;
void InitializeRecipes();
public:
CraftingManager(){
db = ItemDatabase::getInstance();
InitializeRecipes();
}
const int CustomCraft(const std::map<int, int>& items);
const int Craft(int ItemID, Inventory& inventory);
const bool addCraftRecipe(const std::map<int, int>& items, const int ResultItemId);
const CraftRecipe* getRecipeById(int id);
const std::vector<int> getItemUsingIngredient(const int ingredientId) const;
const std::vector<int> getCraftableItems(Inventory& inventory) const;
const std::vector<CraftRecipe>& getAllRecipes();
};
특정 아이템의 제작법을 가져오는 getRecipeById,
직접 레시피를 추가하는 addCraftRecipe
두가지가 추가되었다.
단순한 함수 추가/제거외에 c++문법사용이 조금 개선되었는데
const의 위치나 값전달( & 이용한 전달)사용이 변경되었다.
const에 대해 간단히 설명하다면
기본적으로 const는 상수로 만드는것이고,
const func() : 함수의 앞에 const가 오면 반환값을 변경 불가능,
func() const : 함수의 뒤에 const가 가면 해당 함수 내에서는 클래스 멤버 변수를 상수화하여 변경할 수 없으며
func(const T param) : 해당함수에서 받아온 const파라미터를 상수화하여 변경할 수 없다. 이는 값전달 또는 포인터 전달시에 위험성을 잘못된 변경을 막을 수 있다.
이를 잘 활용하면 값전달을 효율적으로 하면서 수정에 대한 위험을 줄일 수 있다.
당연히도 void는 const를 붙일 필요가 없는데 저번에는 마구잡이로 사용하여 몇개 쓸모없는 const void함수도 있었다...
함수파라미터에는 값전달 & 를 쓸 수 있는데, 메모리를 적게 차지하는 int등 기본 변수형에는 굳이 사용할 필요가 없다고 한다.
그리고 이번에 CraftingManager.h의 getRecipeById함수에서 포인터를 이용했는데,
원래는 포인터를 잘 몰라서 값 전달을 하려고 시도하였었다.
const CraftRecipe& CraftingManager::getRecipeById(int id)
{
for (CraftRecipe rcp : craftRecipes) {
if (rcp.getItemId() == id)
return rcp;
}
return CraftRecipe();
}
처음 작성한 getRecipeById함수인데 멤버 변수인 craftRecipes 벡터로부터 특정 레시피를 찾아 반환하도록 되어잇다.
반환은 const T&를 이용하여 값 전달을 하도록 해두었다.
그런데 이렇게 하니 문제가 호출한 부분에서 받은 값을 다른 함수로 전달하려고 하니 값자기 값이 사라지는(접근불가)것이다!
const CraftRecipe& cr = craftManager.getRecipeById(result);
printRecipe(itemDB, cr);
문제의 코드조각인데, 찾은 레시피 변수 cr을 printRecipe함수로 전달하려하니 사라지는것이다.
왜 그런가 하고 찾아보니 포인터를 사용하지도 않았는데 Dangling Pointer 이슈가 발생하는것이다. 안쓰면 문제없는줄알았는데!
const CraftRecipe& CraftingManager::getRecipeById(int id)
{
for (CraftRecipe rcp : craftRecipes) {// <- rcp는 vector인 craftRecipes를 순회하며 복사한 복사본!
if (rcp.getItemId() == id)
return rcp;//<-반환이 값전달방식이라 rcp는 반환은되나, 호출한 위치를 벗어나면 바로 파괴됨!
}
return CraftRecipe();// <- 실패시 반환할 임시객체는 return하자마자 함수가 종료되며 파괴되는것!
}
다시 getRecipeById로 돌아가서 보면 위의 주석과 같은 상황이다
지역변수의 수명은 그 함수에 한하기 때문에, 이 변수들이 파괴되는건 당연한 상황이었던것
꼴에 반환값을 값전달로 한탓에 호출한 위치에서 읽기는 됬었는데, 이 값을 다른 함수에 전달하니 바로 접근불가가 되버리더라.
이탓에 변수와 씨름하고 웹서핑을 하고나서야 댕글링포인터 이슈인것을 알고 포인터로 변경하였다.
최종수정본은 아래와같다.
const CraftRecipe* CraftingManager::getRecipeById(int id)
{
for (const auto& rcp : craftRecipes) {
if (rcp.getItemId() == id)
return &rcp;
}
return nullptr;
}
반환값은 CraftRecipe* 포인터로 제대로 값전달을 하였으며, 실패시에 반환은 nullptr로 수정되었다.
이 덕에 반환값을 제대로 활용할 수 있으며, 실패시에 대한 처리도 더 명확해졌다!
그리고 드는 생각은 지금 함수들.... 값 사용처를 확인해보고 싹다 고쳐야하겟구나 라는 생각이더라.
C#과 파이썬에 익숙해서 무서운 포인터 대신 값전달을 계속해서 쓰고있었는데,
다시 생각해보니 작성해둔 모든 값전달을 이용하고있는 함수들이 이 이슈를 터트릴 수 있다는 뜻이 되니까...
우선은 필요한 부분에 포인터를 이용해 미연에 같은 문제를 발생할 수 있는 부분을 개선하도록 해야겟다.
다음 목표는 함수에 포인터를 이용한 개선일수도있고,
추상화 적용 및 기능 확장일수도...
부족한 코드에 관심이있다면...
https://github.com/Zerosik/nbc_cpp_project4
GitHub - Zerosik/nbc_cpp_project4: 내일배움캠프 cpp 과제4
내일배움캠프 cpp 과제4. Contribute to Zerosik/nbc_cpp_project4 development by creating an account on GitHub.
github.com
'개발 > 내일배움캠프' 카테고리의 다른 글
| C++ 인벤토리, 제작시스템 구현해보기 5 / 아이템별 최대개수 (0) | 2026.03.19 |
|---|---|
| C++ 인벤토리, 제작시스템 구현해보기 4 / 실제 기능 구현 (0) | 2026.03.18 |
| C++ 인벤토리, 제작시스템 구현해보기 2 (0) | 2026.03.13 |
| C++ 인벤토리, 제작시스템 구현해보기 1 (0) | 2026.03.12 |
| 자주 볼거같은 Character Movement컴포넌트 속성들 기록 (0) | 2026.02.27 |