오늘의 학습 진행 내용
템플릿을 활용하여 Vector 구현하기
필수 기능 가이드
Inventory<T> 클래스 구현
Item 뿐만 아니라, Weapon, Potion 등 어떤 타입의 객체든 저장할 수 있도록 템플릿 클래스로 작성합니다.
Inventorytemplate <typename T>를 사용하여 어떤 타입(T)의 데이터든 처리할 수 있도록 합니다.멤버 변수 (private)
T* pItems_: 아이템 객체들을 저장할 동적 배열을 가리키는 포인터입니다. new T[]를 통해 메모리를 할당받습니다.int capacity_: 인벤토리가 최대로 저장할 수 있는 공간의 크기입니다.int size_: 현재 인벤토리에 저장된 아이템의 실제 개수입니다.생성자, 소멸자
Inventory(int capacity = 10)capacity)을 받으며, 값을 전달하지 않으면 기본값으로 10이 설정됩니다.capacity 값이 들어오면 최소 용량을 1로 보정합니다.new T[capacity_]를 실행하여 아이템을 저장할 메모리 공간을 힙(Heap)에 할당합니다.~Inventory()main 함수 종료 시) 자동으로 호출됩니다.delete[] pItems_를 실행하여 생성자에서 할당했던 메모리를 반드시 해제합니다. 메모리 누수(Memory Leak)를 방지하는 중요한 역할을 합니다.nullptr로 초기화합니다.멤버 함수 (public)
외부에서 인벤토리 객체를 조작하기 위해 사용하는 기능들입니다.
void AddItem(const T& item)size_가 capacity_보다 작을 경우에만 아이템을 추가하고, size_를 1 증가시킵니다.void RemoveLastItem()size_를 1 감소시켜 마지막 아이템에 접근할 수 없도록 만듭니다.int GetSize() constsize_)를 반환합니다.int GetCapacity() constcapacity_)을 반환합니다.void PrintAllItems() constfor 반복문을 이용해 0번 인덱스부터 size_ - 1번 인덱스까지 순회하며, 각 아이템 객체의 PrintInfo() 멤버 함수를 호출합니다.// 정보를 출력하는 멤버 함수
void PrintInfo() const {
cout << "[이름: " << name_ << ", 가격: " << price_ << "G]" << endl;
}구현을 한 뒤에 클래스의 구조는 아래와 같습니다.

도전 기능 가이드
필수 기능을 모두 완료했다면, 실제 게임의 인벤토리처럼 더욱 유연한 기능을 추가해 봅시다!
Inventory(const Inventory<T>& other); (복사 생성자)void Assign(const Inventory<T>& other); (대입 역할을 하는 함수) // 복사 생성자
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;
}
AddItem 기능 확장:AddItem이 호출되면, 기존 capacity의 2배 크기로 인벤토리를 확장하고 새로운 아이템을 추가합니다. (기존 아이템들은 모두 유지되어야 합니다.)Resize 멤버 함수 추가:void Resize(int newCapacity): 인벤토리의 capacity를 newCapacity로 변경합니다.SortItems 멤버 함수 추가:void SortItems(): 인벤토리의 아이템들을 가격 오름차순으로 정렬합니다.std::sort 알고리즘을 활용하며, 정렬 기준을 정의하기 위해 클래스 외부에 별도로 작성된 비교 함수(compareItemsByPrice)를 std::sort에 전달합니다. //두 Item 객체를 받아 가격을 기준으로 어떤 것이 먼저 와야 하는지 판단
bool compareItemsByPrice(const Item& a, const Item& b) {
return a.GetPrice() < b.GetPrice();
}
필수 기능과 도전 기능 구현된 구조는 다음과 같습니다.

Inventory.h
#pragma once
#include <iostream>
#include <string>
#include <algorithm>
#include "compareItemByPrice.h"
using namespace std;
template <typename T>
class Inventory {
public:
/*
* 필수 기능
생성자: `Inventory(int capacity = 10)`**
- 인벤토리 객체가 생성될 때 호출됩니다.
- 매개변수로 인벤토리의 용량(`capacity`)을 받으며,
값을 전달하지 않으면 **기본값으로 10**이 설정됩니다.
size_는 값 0으로 인벤토리 객체 생성 시 존재하는 아이템 없으므로 0으로 초기화
pItems_ 인벤토리 동적 배열은 capcity 용량에 맞게 동적 할당 선언
*/
Inventory(int capacity = 10) : capacity_(capacity),size_(0) {
//cacacity 인벤토리 용량은 논리적으로 0보다 커야되므로 최소값 1로 설정
if (capacity <= 0) {
capacity_ = 1;
}
pItems_ = new T[capacity_];
};
/*
* 도전 기능
복사 생성자
객체가 생성+초기화될 때 호출,
새로운 Inventory 객체가 기존 Inventory 객체로 초기화될 때 호출됩니다.
기존 객체의 capacity_, size_, pItems_ 내용을 새로운 객체에 깊은 복사(Deep Copy)합니다.
예시:
Inventory<int> A;
Inventory<int> B = A; // 복사 생성자 호출
*/
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;
}
/*
* 필수 기능
* 소멸자
인벤토리 객체가 소멸될 때 (예: `main` 함수 종료 시) 자동으로 호출됩니다.
`delete[] pItems_`를 실행하여 생성자에서 할당했던 메모리를 **반드시 해제**합니다. 메모리 누수(Memory Leak)를 방지하는 중요한 역할을 합니다.
안전한 코드를 위해, 메모리 해제 후 포인터를 `nullptr`로 초기화합니다.
*/
~Inventory() {
delete[] pItems_; // 동적 할당 해제
pItems_ = nullptr; // 안전을 위해 nullptr 초기화
cout << "인벤토리 소멸" << endl;
};
/*
`void AddItem(const T& item)`**
* 필수 기능
새로운 아이템을 인벤토리에 추가합니다.
`size_`가 `capacity_`보다 작을 경우에만 아이템을 추가하고,
`size_`를 1 증가시킵니다.
인벤토리가 꽉 찼다면 "인벤토리가 꽉 찼습니다!" 메시지를 출력하고
아무 동작도 하지 않습니다.(도전 기능에서 인벤토리 용량 늘리기로 변경)
* 도전 기능
인벤토리가 꽉 찬 상태에서 AddItem이 호출되면,
기존 capacity의 2배 크기로 인벤토리를 확장하고 새로운 아이템을 추가합니다.
(기존 아이템들은 모두 유지되어야 합니다.)
*/
void AddItem(const T& item) {
if (size_ >= capacity_) {
cout << "인벤토리가 꽉 찼습니다!" << endl;
this->Resize(capacity_ * 2);
}
pItems_[size_++] = item;
}
/*
* 필수 기능
인벤토리의 가장 마지막에 추가된 아이템을 제거합니다.
실제로 메모리를 지우는 것이 아니라,
아이템의 개수를 나타내는 `size_`를 1 감소시켜
마지막 아이템에 접근할 수 없도록 만듭니다.
인벤토리가 비어있다면 "인벤토리가 비어있습니다." 메시지를 출력합니다.
*/
void RemoveLastItem() {
if (size_ > 0) {
size_--;
}
else {
cout << "인벤토리가 비어있습니다."<<endl;
}
}
/*
* 필수 기능
현재 인벤토리에 저장된 아이템의 개수(size_)를 반환합니다.
*/
int GetSize() const { //const 처리로 GetSize() 내부에서는 객체의 멤버를 변경 불가능.
return size_;
}
/*
* 필수 기능
인벤토리의 최대 저장 용량(`capacity_`)을 반환합니다.
*/
int GetCapacity() const { //const 처리로 GetCapacity() 내부에서는 객체의 멤버를 변경 불가능.
return capacity_;
}
/*
필수 기능
인벤토리에 있는 모든 아이템의 정보를 화면에 출력합니다.
`for` 반복문을 이용해 `0`번 인덱스부터 `size_ - 1`번 인덱스까지 순회하며,
각 아이템 객체의 `PrintInfo()` 멤버 함수를 호출함
만약 인벤토리가 비어있다면 "(비어있음)" 메시지를 출력
*/
void printAllItems() const { //const 처리로 printAllItems() 내부에서는 객체의 멤버를 변경 불가능.
if (size_ == 0) {
cout << "인벤토리가 비어있습니다." << endl;
}
else {
for (int i = 0; i < size_; i++) {
pItems_[i].PrintInfo(); //pItems_[i] 객체에 선언된 PrintInfo() 멤버 함수 호출
}
}
}
/*
* 도전 기능
대입 역할을 하는 함수
- 이미 존재하는 객체에 값을 덮어쓸 때 호출
Inventory<int> A , Inventory<Int> B 이런식으로 선언되어 있을 때
예시:
Inventory<int> A, B;
A.Assign(B); // A에 B 내용 복사
*/
*/
void Assign(const Inventory<T>& other) {
if (this == &other) return; // 자기 자신 대입 방지
delete[] pItems_; //기존 메모리 해제
size_ = other.size_;
capacity_ = other.capacity_;
pItems_ = new T[capacity_]; //새로운 배열 할당(대입시킬 배열)
//요소 복사
for (int i = 0; i < size_; ++i) {
pItems_[i] = other.pItems_[i];
}
};
/*
* 도전 기능
인벤토리의 capacity를 newCapacity로 변경
변경 시 기존 배열에 존재하던 아이템들은 그대로 유지한 상태로
배열의 용량만 늘리기 위해서 신규 배열 newCapcity 크기로 동적 할당
후 기존 요소 복사해서 처리
*/
void Resize(int newCapacity) {
if (newCapacity <= 0) return;
T* newItems = new T[newCapacity];
int elementsToCopy = (size_ < newCapacity) ? size_ : newCapacity;
for (int i = 0; i < elementsToCopy; ++i) {
newItems[i] = pItems_[i];
}
delete[] pItems_;
pItems_ = newItems;
capacity_ = newCapacity;
size_ = elementsToCopy;
}
/*
인벤토리의 아이템들을 가격 오름차순으로 정렬합니다.
이 기능은 C++ 표준 라이브러리의 std::sort 알고리즘을 활용하며,
정렬 기준을 정의하기 위해 클래스 외부에 별도로 작성된
비교 함수(compareItemsByPrice)를 std::sort에 전달합니다.
STL algorithm의 sort 가져와서 pItems 동적 배열 포인터로
정렬 시작,끝 범위 지종후
외부 정의된 비교함수 적용하는 형태로 구현
*/
void SortItems() {
sort(pItems_,pItems_+size_, compareItemsByPrice);
}
private:
T* pItems_;
int capacity_;
int size_;
};
복습 내용 중 개념 헷갈리거나 정리하고 싶었던 내용 정리
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Person {
public:
Person() { cout << "Person\n"; }
};
class Student : public Person {
public:
Student() { cout << "Student\n"; }
};
//Smart Pointer 부분이랑 메모리 컨트롤 부분 약한 부분 개념 공부 복습하기
//weak_ptr 사용시 실제 소유권이 필요한 곳만 shared_ptr, 나머지는 weak_ptr로 선언
//weak_ptr은 항상 shared_ptr.lock() 객체 살아있는지(소유권 갖고 있는지) 체크
//B의 소유권은 A가 갖고 있는 형태
struct B;
struct A {
shared_ptr<B> b;
~A() { cout << "~A" << endl; }
};
struct B {
weak_ptr<A> a; // weak_ptr로 변경 해서 순환 참조 해결
~B() { cout << "~B" << endl; }
void CheckA() {
if (auto sp = a.lock()) {
cout << "A is alive" << endl;
} else {
cout << "A is expired" << endl;
}
}
};
//템플릿 함수 호출 시 타입 쓰지 않아도 되는 이유, 함수에 놓은 값을 보고 타입을 맞춰 주기 때문
template <typename T>
T Add(T a, T b)
{
return a + b;
}
//템플릿 함수는 하나의 타입만 받는게 아니라 여러 개의 타입을 받아서 사용 가능
template <typename T1, typename T2>
auto Add(T1 a, T2 b)
{
return a + b;
}
// Add(3, 3.5) 이런 형태로도 사용이 가능
//템플릿 함수 Call By Reference를 통해 원본 변수의 값 변경
template<typename T>
void Swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}
int main(){
int* p1 = new int(10);
int* p2 = p1;
delete p1;
p1 = nullptr;
p2 = nullptr; //없는 주소값 가리키는 포인터가 되버린 p2를 nullptr로 재정의
//Person -> Student (부모 클래스 먼저 호출되고 그 후에 자식 Class 호출)
//unique_ptr 선언
unique_ptr<int> p1_u = make_unique<int>(10);
unique_ptr<int> p2_u;
p2_u = move(p1_u);
cout << "p1 is " << (p1_u ? "not null" : "null") << endl;
cout << "p2 value = " << *p2_u << "\n";
//shared_ptr은 참조 받을때마다 +1이되고 rest을 사용하면 0으로 초기화, {} 끝날 때마다도 -1된다.
std::shared_ptr<int> sp1 = std::make_shared<int>(10);
std::shared_ptr<int> sp2 = sp1; // 참조 카운트 +1
{
std::shared_ptr<int> sp3 = sp1; // 참조 카운트 +1
} //여기서 참조 카운트 -1
//참조 대상이 살아있는지 확인하고 살아있으면 참조카운트+1하고 shared_ptr 생성,
//삭제된 상태면 nullptr shared_ptr이 반환됨
std::weak_ptr<int> wp = sp1;
if (auto sp = wp.lock()) {
// 여기 들어왔다는 건 객체가 살아있다는 뜻
// sp->doSomething(); // 안전
} else {
// 이미 삭제됨
}
auto a = make_shared<A>();
auto b = make_shared<B>();
a->b = b;
b->a = a;
b->CheckA();
cout << "End of main" << endl;
/* //operator로 연산자 함수로 재정의해서 overroading 하는 케이스로 사용 가능
Vec2 operator+(const Vec2& other) const {
//현재 객체(*this)와 x,y와
//othher 객체의 x,y를 각각 더한
//새로운 Vec2 객체를 반환
return Vec2(x+other.x,y+other.y);
}
//정의 되있지 않은 operator의 경우 신규 생성도 가능
ostream& operator<<(ostream& os, const Point& p){
//출력 스트림(os)에 Point 정보 출력
os<<"("<<p.x<<","<<p.y<<")";
//스트림을 다시 반환해야
//cout<<a<<b<<c와 같은 체이닝이 가능
return os;
}
*/
int x = 10;
int y = 20;
Swap(x, y);
cout << "x: " << x << ", y: " << y << endl;
//auto는 “이미 타입이 결정돼 있고, 반복해서 쓰기 싫을 때” 쓰는 도구
//auto는 값 복사 기준이어서 참조와 const는 버리고 처리함
//초기값이 없는 경우 불가능
//decltype은 표현식의 타입을 그대로 가져와라
//decltype은 참조/const를 절대 버리지 않음
;
decltype(x) a_d; // int
decltype((x)) b_d = x; // int& ← 괄호 때문에!
}