C++ 기본기 연습 프로젝트: 카드 덱 브라우저
1. 동적 배열(vector) 직접 구현
답지 안보고 작성한 답안:
#pragma oncetemplate<typename T>class Vector{public: Vector(int _size = 5) : size(_size) { capacity = _size + _size / 2; data = new T[capacity]; } ~Vector() { if (data) { delete[] data; data = nullptr; } } void Add(const T& item) { // 1. check capacity whether full or not if (size == capacity) { // 2. if full expand capacity and move items to new container capacity = capacity + capacity / 2; T* newHouse = new T[capacity]; for (int i = 0; i < capacity; ++i) { newHouse[i] = std::move(data[i]); } delete data; // 힙에 있는 T배열은 어떻게 지움? data = std::move(newHouse); } // 3. if not append on the tail data[size + 1] = item; } void Delete(const T& item) { if (size == 0) return; // 1. find delete target recursively and delete it only once int targetIndex = 0; for (int i = 0; i < size; ++i) { if (data[i] == item) { targetIndex = i; break; } } if (targetIndex) { delete data[targetIndex]; // 2. move left ones forward for (int j = targetIndex + 1; j < size; ++j) data[j - 1] = std::move(data[j]); } }private: int size, capacity; T* data = nullptr;};채점
- data는
T*로 잘 잡았음
private: int size, capacity; T* data = nullptr;size와capacity개념 혼동- 생성자에서
size가 아니라capacity를 받아야 함
Vector(int _size = 5) : size(_size){ capacity = _size + _size / 2; data = new T[capacity];}Add()에서 반복문을capacity가 아니라size까지만 복사해야함
for (int i = 0; i < capacity; ++i){ newHouse[i] = std::move(data[i]);}- 생성자에서
- 배열 포인터 핸들링이 미숙함
- T타입 배열 원소 삭제와 배열 포인터 정리를 혼동하고 있음
data = new T[capacity]; // 생성자에선 T[]로 동적할당...delete data; // 힙에 있는 T배열은 어떻게 지움? - size 증감 누락
- 복사 생성자/대입 연산자 미구현(얕은 복사 위험)
보완
- 생성자
Vector(size_t cap = 5) : size_(0), capacity_(cap){ data = static_cast<T*>(::operator new(sizeof(T) * capacity_)); // raw memory만 확보(T 생성자 호출 x)}- Add를 push_back으로 변경
- lvalue rvalue 오버로딩
- 동적할당 대신 placement new 기법 활용(메모리 할당 o, 초기화 x)
void push_back(const T& value){ if (size_ >= capacity_) reallocate(capacity_ + capacity_ / 2 + 1); new (data + size_) T(value); // placement new ++size_;}void push_back(T&& value){ if (size_ >= capacity_) reallocate(capacity_ + capacity_ / 2 + 1); new (data + size_) T(std::move(value)); ++size_;}std::vector와 유사하게 pop_back 추가
void pop_back(){ assert(size_ > 0); --size_; data[size_].~T(); // 직접 소멸}- 특정 원소 삭제도 STL 자료구조 반복자Iterator 인자로 구현
Iterator erase(Iterator it){ assert(size_ > 0); size_t index = it - data; data[index].~T(); // 제거 대상 소멸 for (size_t i = index; i < size_ - 1; ++i) { new (data + i) T(std::move(data[i + 1])); data[i + 1].~T(); } --size_; return data + index;}궁금증
&var와var.operator->()의 차이
0x1000 : var └── ptr = 0x5000&it: var의 주소
&var == '0x0000'var.operator->(): var 객체 내부 원소(ptr) 주소
var.operator->() == '0x5000'- 포인터끼리 덧셈 뺄셈은 바이트 단위로 결과가 나오지 않는다
int arr[5] = {10,20,30,40,50}; int* data = arr; // 0x1000, 10int* pos = &arr[2]; // 0x1008, 30pos - data // (0x1008 - 0x1000) / sizeof(int) = 8 / 4 = 23, ::operator new/delete는 생성자 호출 없이 메모리만 확보한다(placement new)
T* p = new T;::operator delete(p); // ❌ destructor 안 불림T* p = static_cast<T*>(::operator new(sizeof(T))); delete p; // ❌ 생성자 안 불렸는데 destructor 호출됨2. C++ 스타일 파일 입출력 복습
void LoadCards(const std::string& filename, Vector<Card>& cards){ std::ifstream file(filename); if (!file.is_open()) return; std::string line; std::getline(file, line); // 첫 줄 헤더 건너뛰기 while (std::getline(file, line)) { std::stringstream ss(line); std::string id_str, name, type, cost_str, rarity_str; std::getline(ss, id_str, ','); std::getline(ss, name, ','); std::getline(ss, type, ','); std::getline(ss, cost_str, ','); std::getline(ss, rarity_str, ','); int id = std::stoi(id_str); int cost = std::stoi(cost_str); int rarity = std::stoi(rarity_str); cards.push_back(Card(id, name, type, cost, rarity)); }}getline()의 첫번째 인자는std::string이 아니라std::istream&라는 것file에서getline()으로 잡아놓은 한 줄을while 반복문안에서 각 변수로 찢어주기
3. 콘솔(명령 프롬프트) 커스터마이즈 복습
inline void ColorText(Color foreground, Color background = BLACK){ int color = foreground + background * 16; SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);}inline void PutCursorOnPosition(int x, int y){ COORD pos = { static_cast<short>(x), static_cast<short>(y) }; SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), pos);}SetConsole~(GetStdHandle(STD_OUTPUT_HANDLE), ~)API 봐도봐도 안익숙해짐
4. 원형 순환 테크닉 복습
int main(){ Vector<Card> deck; LoadCards("data.txt", deck); int selected = 0; while (true) { render(deck, selected, (selected - 2 + (int)deck.size()) % (int)deck.size()); int ch = _getch(); if (ch == 0xE0) { ch = _getch(); if (ch == 72) selected = (selected - 1 + deck.size()) % deck.size(); if (ch == 80) selected = (selected + 1) % deck.size(); } if (ch == 27 || ch == 113) break; }}
위 gif처럼 100과 1을 이으려면
selected - offset + deck.size()를deck.size()로 나머지 연산한 값을 offset으로 넣어주면 된다