카테고리 없음

인벤토리 시스템 구현

dkuen 2026. 3. 23. 22:07
  1. 데이터 보관함: Inventory 클래스 설계
    • 템플릿 클래스 선언
      • 아이템, 무기, 포션 등 어떤 타입이든 담을 수 있게 만듭니다.
      • 클래스 이름: Inventory
      • 템플릿: template 를 사용하여 어떤 타입(T)의 데이터든 처리할 수 있도록 합니다.
    • 내부 멤버 변수 구성 (Private)
      • T* pItems_: 아이템 객체들을 저장할 동적 배열을 가리키는 포인터입니다.
        • new T[]를 통해 메모리를 할당받습니다.
      • int capacity_: 인벤토리가 최대로 저장할 수 있는 공간의 크기입니다.
      • int size_: 현재 인벤토리에 저장된 아이템의 실제 개수입니다.

  1. 탄생과 소멸: 생성자와 소멸자
    • 생성자 구현 : Inventory(int capacity = 10)
      • 인벤토리 객체가 생성될 때 호출됩니다.
      • 매개변수로 용량(capacity)을 받습니다. (입력하지 않을 경우 기본 10으로 설정)
        • 예외처리 : 유저가 용량을 0 이하로 입력하면, 최소 용량이 1은 되도록 자동으로 보정합니다.
      • new T[capacity_]를 통해 아이템을 저장할 메모리 공간을 힙(Heap) 메모리에 할당합니다.
    • 소멸자 관리 : ~Inventory()
      • 인벤토리 객체가 소멸될 때 (예: main 함수 종료 시) 자동으로 호출됩니다.
      • 메모리 누수 방지 : 객체가 사라질 때 delete[] pItems_를 실행하여 할당받은 메모리를 반납합니다.
      • 해제 후 포인터를 nullptr로 초기화하여 안전성을 높입니다.
      • 안전한 코드를 위해, 메모리 해제 후 포인터를 nullptr로 초기화합니다.

  1. 인벤토리 조작 기능
    • 아이템 추가 (void AddItem(const T& item))
      • 새로운 아이템을 인벤토리에 추가합니다.
      • 인벤토리에 여유 공간이 있을 때만 아이템을 저장하고 size_를 1 늘립니다.
      • 공간이 꽉 찼다면 "인벤토리가 꽉 찼습니다!"라는 경고를 띄웁니다.
    • 마지막 아이템 제거 (void RemoveLastItem())
      • 인벤토리의 가장 마지막에 추가된 아이템을 제거합니다.
      • 실제로 메모리를 지우는 것이 아니라, 아이템의 개수를 나타내는 size_를 1 감소시켜 마지막 아이템에 접근할 수 없도록 만듭니다.
      • 비어있을 때 제거를 시도하면 "인벤토리가 비어있습니다."라고 안내합니다.
    • 정보 조회 함수
      • int GetSize() const: 현재 인벤토리의 크기(몇 개가 들었는지) 반환합니다.
      • int GetCapacity() const: 현재 인벤토리에 최대 용량을 반환합니다.

  1. 목록 출력 기능
    • 전체 출력 (PrintAllItems)
      • 반복문(for)을 사용하여 0번부터 size_ - 1번까지의 모든 아이템 정보를 보여줍니다.
      • 각 아이템 객체의 PrintInfo() 멤버 함수를 호출합니다.
      • *// 정보를 출력하는 멤버 함수 void PrintInfo() const { cout << "[이름: " << name_ << ", 가격: " << price_ << "G]" << endl; }
      • 비어있다면 "비어있음"을 출력합니다.

구현

Inventory.h / Inventory.tpp

더보기

//Inventory.h
#pragma once

template <typename T>
class Inventory {
public:
    Inventory(int capacity = 10);
    ~Inventory();
    void AddItem(const T& item);
    void RemoveLastItem();
    int GetSize() const;
    int GetCapacity() const;
    void PrintALLItems() const;
private:
    T* pItems_;
    int capacity_;
    int size_;
};

#include "Inventory.tpp"
//Inventory.tpp
#include <iostream>

template <typename T>
Inventory<T>::Inventory(int capacity)
    : capacity_(capacity <= 0 ? 1 : capacity), size_(0)
{
    pItems_ = new T[this->capacity_];
}

template <typename T>
Inventory<T>::~Inventory() {
    delete[] pItems_;
    pItems_ = nullptr;
}

template <typename T>
void Inventory<T>::AddItem(const T& item) {
    if (size_ >= capacity_) {
        std::cout << "인벤토리가 꽉 찼습니다!" << std::endl;
        return;
    }
    else {
        pItems_[size_] = item;
        ++size_;
    }
}

template <typename T>
void Inventory<T>::RemoveLastItem() {
    if (size_ == 0) {
        std::cout << "인벤토리가 비어있습니다." << std::endl;
        return;
    }
    else {
        --size_;
    }
}

template <typename T>
int Inventory<T>::GetSize() const {
    return size_;
}

template <typename T>
int Inventory<T>::GetCapacity() const {
    return capacity_;
}

template <typename T>
void Inventory<T>::PrintALLItems() const {
    if (size_ == 0) {
        std::cout << "비어있음" << std::endl;
        return;
    }
    for (int i = 0; i < size_; i++) {
        pItems_[i].PrintInfo();
    }
}

.h, .cpp도 아닌 .tpp를 사용했습니다

먼저 구현을 끝내고 빌드를 하는 과정에서 exe파일이 안만들어졋다는 에러가 발생했습니다

해당 원인을 찾기 위해서 구글링을 하던 와중

템플릿 클래스는 tpp를 사용해서 선언부분과 구현부분을 나누어 줘야 한다는 것을 발견하였습니다

이유는 템플릿 클래스는 자료형이 정해지는 시점에서 코드를 생성해야 하는데, 이 때 구현이 안보이면 컴파일러가 완전한 코드를 만들 수 없다는 것입니다

그럼 tpp는 뭐냐?

tpp는 사실 별 의미는 없고 템플릿 클래스의 구현 부분인 것을 나타내는 텍스트 파일입니다.

진짜 해결 방법은?

사실 템플릿 클래스를 헤더파일에 선언과 동시에 구현하는 것이 가장 현명한 방법입니다.

해당 방법은 그냥 깔끔하게 보인다.. 혹은 다른 클래스의 선언과 구현을 비슷하게 만들 수 있다. 라고 볼 수 있습니다.

Item.h / Item.cpp

더보기

//Item.h
#pragma once
#include<string>

class Item {
public:
    Item(std::string name = "none", int price = 0);
    std::string getName() const;
    int getPrice() const;
    void setName(std::string newName);
    void setPrice(int newPrice);
    void PrintInfo() const;
private:
    std::string name_;
    int price_;
};
//Item.cpp
#include "Item.h"
#include<iostream>

Item::Item(std::string name, int price)
    : name_(name), price_(price) {}

std::string Item::getName() const {
    return name_;
}

int Item::getPrice() const {
    return price_;
}

void Item::setName(std::string newName) {
    name_ = newName;
}

void Item::setPrice(int newPrice) {
    price_ = newPrice;
}

void Item::PrintInfo() const {
    std::cout << "[이름: " << name_ << ", 가격: " << price_ << "G]" << std::endl;
}

Item 클래스는 PrintInfo() 때문에 만들었습니다.

main.cpp

더보기

#include <iostream>
#include"Inventory.h"
#include"Item.h"

int main()
{
    Item sword("sword", 100);
    Inventory<Item> inv(5);
    inv.AddItem(sword);
    inv.PrintALLItems();
}

추가 구현

  1. 데이터 안전하게 복사하기
    • 복사 생성자 구현 (Inventory(const Inventory& other);)
      • 새로운 객체가 만들어지거나 초기화 될 때, 다른 객체의 데이터를 그대로 복사해옵니다.
      • 단순히 주소만 복사하는 것이 아니라, new T[capacity_]를 통해 새로운 메모리 공간을 만들고 내용을 하나씩 옮겨 담아야 합니다. (깊은 복사)
      // 복사 생성자
      Inventory(const Inventory<T>& other) {
          capacity_ = other.capacity_;
          size_ = other.size_;
          pItems_ = new T[capacity_];
          for (int i = 0; i < size_; ++i) {
              pItems_[i] = other.pItems_[i];
          }
          cout << "인벤토리 복사 완료" << endl;
      }
    • Assign(대입) 함수 구현 (void Assign(const Inventory& other);)
      • 이미 만들어진 객체에 다른 객체의 내용을 덮어씁니다.
      • 기존에 가지고 있던 메모리는 delete[]로 먼저 비워준 뒤, 새로운 공간을 만들어 복사해야 메모리 누수가 생기지 않습니다.

  1. 인벤토리 자동 확장 (Resize)
    • Resize 멤버 함수 구현
      • 새로운 크기(newCapacity)만큼의 새로운 메모리 공간을 할당합니다.
      • 기존에 들어있던 아이템들을 새 공간으로 모두 이사시킨 뒤, 낡은 메모리는 해제합니다.
    • AddItem 기능 업그레이드
      • 아이템을 넣으려는데 인벤토리가 꽉 찼다면?
      • 프로그램을 멈추지 말고 Resize를 호출하여 기존 용량의 2배로 공간을 늘린 후 아이템을 추가합니다.

  1. 아이템 정렬 기능 (Sort)
    • 비교 함수(compareItemsByPrice) 작성
      • 클래스 외부에 두 아이템의 가격을 비교하는 함수를 만듭니다. (왼쪽 아이템 가격이 더 작으면 true 반환)
    • SortItems 함수 구현
      • 배열의 시작 주소(pItems_), 끝 지점(pItems_ + size_), 그리고 위에서 만든 비교 함수를 전달하여 **가격순(오름차순)**으로 정렬합니다.
      • C++ 표준 라이브러리인 std::sort를 활용합니다.
      • 정렬 기준 정의 : 클래스 외부에 별도로 작성된 비교 함수(compareItemsByPrice)를 std::sort에 전달합니다.

구현

Inventory.h

더보기
//Inventory.h
#pragma once
#include<iostream>

template <typename T>
class Inventory {
public:
	Inventory(int capacity = 10);
	Inventory(const Inventory<T>& other);
	~Inventory();
	void AddItem(const T& item);
	void RemoveLastItem();
	int GetSize() const;
	int GetCapacity() const;
	T* GetItems() const;
	void PrintALLItems() const;
	void Assign(const Inventory<T>& other);
	void Resize(int newCapacity);
private:
	T* pItems_;
	int capacity_;
	int size_;
};

template <typename T>
Inventory<T>::Inventory(int capacity)
	: capacity_(capacity <= 0 ? 1 : capacity), size_(0)
{
	pItems_ = new T[this->capacity_];
}

template <typename T>
Inventory<T>::Inventory(const Inventory<T>& other) {
	capacity_ = other.capacity_;
	size_ = other.size_;
	pItems_ = new T[capacity_];
	for (int i = 0; i < size_; ++i) {
		pItems_[i] = other.pItems_[i];
	}
	std::cout << "인벤토리 복사 완료" << std::endl;
}

template <typename T>
Inventory<T>::~Inventory() {
	delete[] pItems_;
	pItems_ = nullptr;
}

template <typename T>
void Inventory<T>::AddItem(const T& item) {
	if (size_ >= capacity_) {
		std::cout << "인벤토리가 꽉 찼습니다!" << std::endl;
		Resize(capacity_ * 2);
	}
	pItems_[size_] = item;
	++size_;
}

template <typename T>
void Inventory<T>::RemoveLastItem() {
	if (size_ == 0) {
		std::cout << "인벤토리가 비어있습니다." << std::endl;
		return;
	}
	else {
		--size_;
	}
}

template <typename T>
int Inventory<T>::GetSize() const {
	return size_;
}

template <typename T>
int Inventory<T>::GetCapacity() const {
	return capacity_;
}

template <typename T>
T* Inventory<T>::GetItems() const {
	return pItems_;
}

template <typename T>
void Inventory<T>::PrintALLItems() const {
	if (size_ == 0) {
		std::cout << "비어있음" << std::endl;
		return;
	}
	for (int i = 0; i < size_; i++) {
		pItems_[i].PrintInfo();
	}
}

template <typename T>
void Inventory<T>::Assign(const Inventory<T>& other) {
	delete[] pItems_;
	capacity_ = other.capacity_;
	size_ = other.size_;
	pItems_ = new T[capacity_];
	for (int i = 0; i < size_; ++i) {
		pItems_[i] = other.pItems_[i];
	}
}

template <typename T>
void Inventory<T>::Resize(int newCapacity) {
	if (newCapacity <= 0) {
		newCapacity = 1;
	}
	T* oldItems = pItems_;
	int copyCount = (size_ < newCapacity) ? size_ : newCapacity;
	pItems_ = new T[newCapacity];
	for (int i = 0; i < copyCount; ++i) {
		pItems_[i] = oldItems[i];
	}

	delete[] oldItems;

	capacity_ = newCapacity;
	size_ = copyCount;
}

변경점은 복사생성자의 추가, GetItems()추가 Assign() 멤버함수 Resize()의 추가이다.

 

Main.cpp

더보기
#include <iostream>
#include <algorithm>
#include"Inventory.h"
#include"Item.h"

bool compareItemsByPrice(const Item& a, const Item& b) {
    return a.getPrice() < b.getPrice();
}

void SortItems(Inventory<Item>& a) {
    std::sort(a.GetItems(), a.GetItems() + a.GetSize(), compareItemsByPrice);
}

int main()
{
    Item sword("sword", 100);
    Item potion("potion", 20);
    Item armor("armor", 500);
    Item shield("shield", 200);
    Inventory<Item> inv(5);
    inv.AddItem(sword);
    inv.AddItem(potion);
    inv.AddItem(armor);
    inv.AddItem(shield);
    inv.PrintALLItems();
    SortItems(inv);
    std::cout << "-------------------------" << std::endl;
    inv.PrintALLItems();
}