[Day16] InventorySystem_Required with C++ Templates

베리투스·2025년 8월 26일

TIL: Today I Learned

목록 보기
24/93

오늘은 C++의 강력한 기능인 템플릿을 사용해 어떤 아이템이든 담을 수 있는 만능 인벤토리 클래스를 만들어봤다. 👜 동적 메모리 할당(new[])과 해제(delete[])를 직접 관리하며 메모리 누수(Memory Leak)를 막는 법을 익혔다. 생성자와 소멸자의 역할, 그리고 멤버 함수를 통해 클래스의 데이터를 안전하게 조작하는 객체지향의 기본 원칙을 코드로 직접 구현하며 제대로 체득할 수 있었다. 💪


📌 목표

  • 템플릿(template <typename T>)을 이용한 범용 클래스 설계
  • new[]delete[]를 사용한 동적 메모리 관리
  • 생성자(기본 인자 포함)와 소멸자의 역할 이해
  • 클래스의 기본 멤버 함수(AddItem, RemoveLastItem 등) 구현

💻 코드

InventorySystem_Required.cpp

#include <iostream>
#include <string>

using namespace std;

// 인벤토리에 저장될 아이템을 표현하는 클래스입니다.
class Item {
public:
    // 생성자: Item 객체가 생성될 때 이름과 가격을 초기화합니다.
    Item(string name = "없음", int price = 0)
        : m_name(name), m_price(price) {}

    // 아이템의 정보를 출력하는 멤버 함수입니다.
    void PrintInfo() const {
        cout << "[이름: " << m_name << ", 가격: " << m_price << "G]" << endl;
    }

private:
    // 멤버 변수(Member Variable)임을 나타내기 위해 'm_' 접두사를 사용합니다.
    string m_name; // 아이템 이름
    int m_price;   // 아이템 가격
};


// 어떤 타입(T)의 객체든 저장할 수 있는 템플릿 기반의 인벤토리 클래스입니다.
template <typename T>
class Inventory {
public:
    // 1. 생성자 (Constructor)
    // 인벤토리 객체를 생성할 때 호출됩니다.
    // 용량(capacity)을 인자로 받으며, 값을 주지 않으면 기본값 10이 사용됩니다.
    Inventory(int capacity = 10) {
        // 만약 0 이하의 잘못된 용량 값이 들어오면, 최소 용량인 1로 보정합니다.
        if (capacity <= 0) {
            cout << "잘못된 용량 값(" << capacity << ")이 입력되어 최소 용량 1로 설정합니다." << endl;
            m_capacity = 1;
        } else {
            m_capacity = capacity;
        }

        // 현재 아이템 개수는 0개로 반드시 초기화해야 합니다.
        m_size = 0;

        // 'new T[m_capacity]'를 통해 T 타입의 객체를 m_capacity 개수만큼 저장할 수 있는
        // 메모리 공간을 힙(Heap) 영역에 동적으로 할당하고, 그 주소를 m_pItems 포인터에 저장합니다.
        m_pItems = new T[m_capacity];

        cout << "인벤토리 생성 완료! (최대 용량: " << m_capacity << ")" << endl;
    }

    // 2. 소멸자 (Destructor)
    // 인벤토리 객체가 메모리에서 사라질 때 (예: main 함수 종료 시) 자동으로 호출됩니다.
    ~Inventory() {
        cout << "인벤토리 소멸 시작. 할당된 메모리를 해제합니다." << endl;
        
        // 생성자에서 'new[]'로 할당했던 메모리를 'delete[]'로 반드시 해제합니다.
        // 이를 통해 메모리 누수(Memory Leak)를 방지할 수 있습니다.
        delete[] m_pItems;

        // 이미 해제된 메모리를 가리키는 것을 방지하기 위해 포인터를 nullptr로 초기화합니다. (안전한 코딩 습관)
        m_pItems = nullptr;
    }

    // 3. 아이템 추가 함수
    void AddItem(const T& item) {
        // 인벤토리에 공간이 남아 있는지 확인합니다. (m_size < m_capacity)
        if (m_size < m_capacity) {
            // m_pItems 배열의 다음 빈 공간(m_size 인덱스)에 새 아이템을 저장합니다.
            m_pItems[m_size] = item;
            // 아이템을 추가했으므로, 현재 아이템 개수(m_size)를 1 증가시킵니다.
            m_size++;
            cout << "아이템 추가 완료!" << endl;
        } else {
            // 인벤토리가 꽉 찼을 경우 메시지를 출력합니다.
            cout << "인벤토리가 꽉 찼습니다! 아이템을 추가할 수 없습니다." << endl;
        }
    }

    // 4. 마지막 아이템 제거 함수
    void RemoveLastItem() {
        // 인벤토리에 아이템이 하나라도 있는지 확인합니다. (m_size > 0)
        if (m_size > 0) {
            // 가장 마지막 아이템은 m_size - 1 인덱스에 있습니다.
            // 실제로 메모리에서 데이터를 지우는 대신, 아이템 개수를 1 줄여서
            // 마지막 아이템에 접근할 수 없게 만드는 방식입니다.
            m_size--;
            cout << "마지막 아이템 제거 완료!" << endl;
        } else {
            // 인벤토리가 비어있을 경우 메시지를 출력합니다.
            cout << "인벤토리가 비어있습니다. 제거할 아이템이 없습니다." << endl;
        }
    }

    // 5. 현재 아이템 개수 반환 함수
    int GetSize() const {
        return m_size;
    }

    // 6. 최대 용량 반환 함수
    int GetCapacity() const {
        return m_capacity;
    }

    // 7. 모든 아이템 정보 출력 함수
    void PrintAllItems() const {
        cout << "\n--- 인벤토리 아이템 목록 ---" << endl;
        if (m_size == 0) {
            cout << "(비어있음)" << endl;
        } else {
            for (int i = 0; i < m_size; ++i) {
                cout << i + 1 << ". ";
                m_pItems[i].PrintInfo();
            }
        }
        cout << "--------------------------" << endl;
        cout << "(현재 아이템 개수: " << m_size << " / 최대 용량: " << m_capacity << ")\n" << endl;
    }

private:
    T* m_pItems;       // 아이템 객체들을 저장할 동적 배열을 가리키는 포인터
    int m_capacity;    // 인벤토리의 최대 저장 공간 크기
    int m_size;        // 현재 저장된 아이템의 개수
};

⚠️ 실수

  • delete[]에서 대괄호 []를 빼먹어서 한참 헤맸다. 😭 new T[]로 배열을 할당했으면 해제할 때도 꼭 delete[]를 써야 하는데, 그냥 delete만 썼더니 프로그램이 종료될 때 이상한 에러가 났다. 메모리 누수의 공포를 처음으로 느꼈다.
  • 생성자에서 m_size = 0; 초기화를 깜빡했더니 AddItem을 한 번만 했는데도 인벤토리가 꽉 찼다는 메시지가 뜨는 것이었다. 🤯 알고 보니 m_size쓰레기 값이 들어가 있어서 if (m_size < m_capacity) 조건이 바로 거짓이 되어버렸다. 변수 초기화는 정말 기본 중의 기본인데... 반성했다.
  • 인벤토리 꽉 찼는지 검사할 때 m_size < m_capacity라고 해야 하는데, 무심코 m_size <= m_capacity라고 썼다가 배열의 범위를 벗어나는 실수를 할 뻔했다. 😱 m_capacity가 10이면 인덱스는 0부터 9까지인데, m_size가 10일 때도 아이템을 추가하려고 했던 것이다. 이런 사소한 실수가 큰 버그를 만든다는 걸 깨달았다.

✅ 핵심 요약

개념설명비고
템플릿 (Template)클래스나 함수를 특정 자료형에 의존하지 않고, 범용적으로 만들 수 있게 하는 C++의 기능.template <typename T> 선언 후, Tint, string, Item 등 원하는 타입으로 대체하여 사용.
동적 메모리 할당프로그램 실행 중에 필요한 만큼의 메모리를 힙(Heap) 영역에 할당받는 것.new T[크기]로 할당. 할당된 메모리의 주소를 포인터 변수에 저장하여 사용한다.
생성자 (Constructor)객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수. 주로 멤버 변수를 초기화하는 역할을 한다.Inventory(int capacity = 10)처럼 기본 인자를 설정하면 객체 생성 시 유연성을 높일 수 있다.
소멸자 (Destructor)객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수. 주로 동적으로 할당한 메모리를 해제하는 역할을 한다.~Inventory() 형태이며, delete[]를 사용하여 메모리 누수(Memory Leak)를 반드시 막아야 한다.
const 멤버 함수멤버 변수의 값을 변경하지 않겠다고 약속하는 함수.int GetSize() const와 같이 함수 뒤에 const를 붙인다. 코드의 안정성과 가독성을 높여준다.
profile
Shin Ji Yong // Unreal Engine 5 공부중입니다~

0개의 댓글