오늘은 C++의 강력한 기능인 템플릿을 사용해 어떤 아이템이든 담을 수 있는 만능 인벤토리 클래스를 만들어봤다. 👜 동적 메모리 할당(new[])과 해제(delete[])를 직접 관리하며 메모리 누수(Memory Leak)를 막는 법을 익혔다. 생성자와 소멸자의 역할, 그리고 멤버 함수를 통해 클래스의 데이터를 안전하게 조작하는 객체지향의 기본 원칙을 코드로 직접 구현하며 제대로 체득할 수 있었다. 💪
template <typename T>)을 이용한 범용 클래스 설계new[]와 delete[]를 사용한 동적 메모리 관리#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> 선언 후, T를 int, string, Item 등 원하는 타입으로 대체하여 사용. |
| 동적 메모리 할당 | 프로그램 실행 중에 필요한 만큼의 메모리를 힙(Heap) 영역에 할당받는 것. | new T[크기]로 할당. 할당된 메모리의 주소를 포인터 변수에 저장하여 사용한다. |
| 생성자 (Constructor) | 객체가 생성될 때 자동으로 호출되는 특별한 멤버 함수. 주로 멤버 변수를 초기화하는 역할을 한다. | Inventory(int capacity = 10)처럼 기본 인자를 설정하면 객체 생성 시 유연성을 높일 수 있다. |
| 소멸자 (Destructor) | 객체가 소멸될 때 자동으로 호출되는 특별한 멤버 함수. 주로 동적으로 할당한 메모리를 해제하는 역할을 한다. | ~Inventory() 형태이며, delete[]를 사용하여 메모리 누수(Memory Leak)를 반드시 막아야 한다. |
const 멤버 함수 | 멤버 변수의 값을 변경하지 않겠다고 약속하는 함수. | int GetSize() const와 같이 함수 뒤에 const를 붙인다. 코드의 안정성과 가독성을 높여준다. |