Company
교육 철학

LV 13 std::vector클래스 만들기

#include <iostream> #include <algorithm> namespace ya { template <typename T> class Vector { public: // 🏗️ 기본 생성자 Vector(size_t cap = DEFAULT_CAP) : mArr(new T[cap]) , mSize(0) , mCapacity(cap) { } // 🏗️ 복사 생성자 Vector(const Vector& other) : mArr(new T[other.mCapacity]) , mSize(other.mSize) , mCapacity(other.mCapacity) { for (size_t i = 0; i < mSize; i++) { mArr[i] = other[i]; } } // 🗑️ 소멸자 ~Vector() { delete[] mArr; } // 📝 대입 연산자 Vector& operator=(const Vector& other) { if (this != &other) { if (mCapacity < other.mCapacity) { delete[] mArr; mCapacity = other.mCapacity; mArr = new T[mCapacity]; } mSize = other.mSize; for (size_t i = 0; i < mSize; i++) mArr[i] = other.mArr[i]; } return *this; } // 🎯 접근 함수들 T& operator[](size_t idx) { return mArr[idx]; } const T& operator[](size_t idx) const { return mArr[idx]; } T& front() { return mArr[0]; } T& back() { return mArr[mSize - 1]; } // 🔄 반복자 T* begin() const { return mArr; } T* end() const { return mArr + mSize; } // ➕ 요소 조작 void push_back(const T& value) { if (mSize >= mCapacity) // 용량 부족 시 { size_t newCap = mCapacity < DEFAULT_CAP ? DEFAULT_CAP : mCapacity * 2; T* newArr = new T[newCap]; for (size_t i = 0; i < mSize; i++) { newArr[i] = mArr[i]; } delete[] mArr; mArr = newArr; mCapacity = newCap; } mArr[mSize++] = value; } void pop_back() { if (mSize > 0) mSize--; } void resize(size_t n, T value = T()) { T* newArr = new T[n]; size_t copySize = mSize < n ? mSize : n; for (size_t i = 0; i < copySize; i++) newArr[i] = mArr[i]; for (size_t i = copySize; i < n; i++) newArr[i] = value; delete[] mArr; mArr = newArr; mSize = n; mCapacity = n; } void clear() { mSize = 0; } // 📊 정보 조회 size_t capacity() const { return mCapacity; } size_t size() const { return mSize; } bool empty() const { return mSize == 0; } // 🔍 비교 연산자 bool operator==(const Vector& other) const { if (mSize != other.mSize) return false; for (size_t i = 0; i < mSize; i++) if (mArr[i] != other[i]) return false; return true; } bool operator!=(const Vector& other) const { return !(*this == other); } private: static constexpr size_t DEFAULT_CAP = 32; T* mArr; size_t mSize; size_t mCapacity; }; } int main() { // 🌟 기본 사용법 ya::Vector<int> vec; std::cout << "=== 기본 동작 테스트 ===" << std::endl; std::cout << "초기 크기: " << vec.size() << std::endl; std::cout << "초기 용량: " << vec.capacity() << std::endl; // ➕ 요소 추가 for (int i = 1; i <= 5; i++) { vec.push_back(i); } std::cout << "\n5개 요소 추가 후:" << std::endl; std::cout << "크기: " << vec.size() << std::endl; std::cout << "용량: " << vec.capacity() << std::endl; // 🖨️ 요소 출력 std::cout << "요소들: "; for (size_t i = 0; i < vec.size(); i++) { std::cout << vec[i] << " "; } std::cout << std::endl; // 🔄 반복자 사용 std::cout << "반복자로 출력: "; for (auto it = vec.begin(); it != vec.end(); ++it) { std::cout << *it << " "; } std::cout << std::endl; // 📏 크기 변경 std::cout << "\n=== resize 테스트 ===" << std::endl; vec.resize(15, 99); // 크기를 15로, 새 요소는 99로 초기화 vec[0] = 2; // 첫 번째 요소 변경 vec[10] = 2; // 10번째 요소 변경 std::cout << "resize(15, 99) 후:" << std::endl; std::cout << "크기: " << vec.size() << std::endl; std::cout << "처음 15개 요소: "; for (size_t i = 0; i < vec.size(); i++) { std::cout << vec[i] << " "; } std::cout << std::endl; // 🔍 접근 함수 테스트 std::cout << "\n=== 접근 함수 테스트 ===" << std::endl; std::cout << "첫 번째 요소: " << vec.front() << std::endl; std::cout << "마지막 요소: " << vec.back() << std::endl; // 🏗️ 복사 생성자 테스트 std::cout << "\n=== 복사 생성자 테스트 ===" << std::endl; ya::Vector<int> vec2 = vec; // 복사 생성자 호출 std::cout << "원본과 복사본 비교: " << (vec == vec2 ? "같음" : "다름") << std::endl; vec2[0] = 100; // 복사본 수정 std::cout << "복사본 수정 후 비교: " << (vec == vec2 ? "같음" : "다름") << std::endl; std::cout << "원본 첫 요소: " << vec[0] << std::endl; std::cout << "복사본 첫 요소: " << vec2[0] << std::endl; return 0; }
C++
복사

커스텀 Vector 클래스 구현

std::vector 완전 구현

동적 배열을 구현한 완전한 컨테이너 클래스
이전 시간에 배운 동적할당과 템플릿을 활용하여 std::vector와 유사한 기능을 모두 구현한 클래스입니다.

클래스 구조

멤버 변수

private: static constexpr size_t DEFAULT_CAP = 32; // 🔢 기본 용량 T* mArr; // 📦 실제 데이터 배열 포인터 size_t mSize; // 📏 현재 저장된 요소 개수 size_t mCapacity; // 🏠 할당된 전체 용량
C++
복사
메모리 구조:
mCapacity: [1][2][3][4][ ][ ][ ][ ]...[ ] (32칸 할당) mSize: 4 ↑─────────────↑ (실제 사용 4칸) mArr: 포인터가 첫 번째 요소를 가리킴
Plain Text
복사

생성자들

기본 생성자

Vector(size_t cap = DEFAULT_CAP) : mArr(new T[cap]) // 🆕 동적 배열 할당 , mSize(0) // 📏 초기 크기는 0 , mCapacity(cap) // 🏠 용량 설정 { }
C++
복사

복사 생성자 (깊은 복사)

Vector(const Vector& other) : mArr(new T[other.mCapacity]) // 🆕 새로운 메모리 할당 , mSize(other.mSize) // 📏 크기 복사 , mCapacity(other.mCapacity) // 🏠 용량 복사 { // 🔄 모든 요소를 하나씩 복사 for (size_t i = 0; i < mSize; i++) { mArr[i] = other[i]; } }
C++
복사

이동 생성자 (C++11)

Vector(Vector&& other) : mArr(std::move(other.mArr)) // 📦 소유권 이동 , mSize(other.mSize) // 📏 크기 이동 , mCapacity(other.mCapacity) // 🏠 용량 이동 { // 🧹 원본 객체 정리 other.mArr = nullptr; other.mSize = 0; other.mCapacity = 0; }
C++
복사

소멸자

~Vector() { delete[] mArr; // 🗑️ 동적 할당된 메모리 해제 }
C++
복사

대입 연산자들

복사 대입 연산자

Vector& operator=(const Vector& other) { if (this != &other) // 🚫 자기 자신 대입 방지 { // 🏠 용량이 부족하면 재할당 if (mCapacity < other.mCapacity) { delete[] mArr; mCapacity = other.mCapacity; mArr = new T[mCapacity]; } // 📋 데이터 복사 mSize = other.mSize; for (size_t i = 0; i < mSize; i++) mArr[i] = other.mArr[i]; } return *this; }
C++
복사

이동 대입 연산자

Vector& operator=(Vector&& other) { // 🔄 모든 멤버를 교환 std::swap(mArr, other.mArr); std::swap(mSize, other.mSize); std::swap(mCapacity, other.mCapacity); return *this; }
C++
복사

접근 및 반복자 함수들

T* begin() const { return mArr; } // 🏁 시작 포인터 T* end() const { return mArr + mSize; } // 🏁 끝 포인터 T& front() { return mArr[0]; } // 🔝 첫 번째 요소 T& back() { return mArr[mSize - 1]; } // 🔚 마지막 요소 T& operator[](size_t idx) { return mArr[idx]; } // 🎯 인덱스 접근
C++
복사

요소 조작 함수들

push_back 구현

void push_back(const T& value) { // 🚨 용량 부족 시 확장 if (mSize >= mCapacity) // ⚠️ 원본 코드 수정 필요 { if (mCapacity < DEFAULT_CAP) mCapacity = DEFAULT_CAP; else mCapacity *= 2; // 📈 2배로 확장 // 🔄 새 배열로 이주 T* newArr = new T[mCapacity]; for (size_t i = 0; i < mSize; i++) { newArr[i] = mArr[i]; } delete[] mArr; mArr = newArr; } mArr[mSize++] = value; // ➕ 요소 추가 }
C++
복사

기타 조작 함수들

void pop_back() { mSize = mSize > 0 ? mSize - 1 : 0; // 📉 크기만 감소 } void resize(size_t n, T value = T()) { T* newArr = new T[n]; // 🆕 새 크기로 할당 size_t copySize = mSize < n ? mSize : n; // 🔄 기존 데이터 복사 for (size_t i = 0; i < copySize; i++) newArr[i] = mArr[i]; // 🆕 새 요소들은 기본값으로 초기화 for (size_t i = copySize; i < n; i++) newArr[i] = value; delete[] mArr; mArr = newArr; mSize = n; mCapacity = n; } void clear() { mSize = 0; } // 🧹 논리적 삭제
C++
복사

정보 조회 함수들

size_t capacity() const { return mCapacity; } // 🏠 전체 용량 size_t size() const { return mSize; } // 📏 현재 크기 bool empty() const { return mSize == 0; } // 🤔 비어있는지 확인
C++
복사

비교 연산자들

bool operator==(const Vector& other) const { if (mSize != other.mSize) // 📏 크기가 다르면 false return false; // 🔍 모든 요소 비교 for (size_t i = 0; i < mSize; i++) // ⚠️ 원본 코드 수정 필요 if (mArr[i] != other[i]) return false; return true; } bool operator!=(const Vector& other) const { return !(*this == other); // ❗ == 연산자의 반대 } bool operator<(const Vector& other) { // 📖 사전식 순서로 비교 (lexicographic comparison) size_t minSize = mSize < other.mSize ? mSize : other.mSize; for (size_t i = 0; i < minSize; i++) { if (mArr[i] != other[i]) return mArr[i] < other[i]; // 🔍 첫 다른 요소로 판단 } return mSize < other.mSize; // 📏 길이로 판단 }
C++
복사

핵심 개념 정리

RAII (Resource Acquisition Is Initialization)

생성자에서 자원 할당, 소멸자에서 자원 해제
메모리 누수 방지의 핵심 원칙

용량 관리 전략

// 📈 지수적 증가 (Exponential Growth) if (mCapacity < DEFAULT_CAP) mCapacity = DEFAULT_CAP; // 처음엔 기본값 else mCapacity *= 2; // 이후엔 2배씩 증가
C++
복사
장점: 빈번한 재할당을 줄여 성능 향상

깊은 복사 vs 얕은 복사

얕은 복사: 포인터만 복사 (위험!)
깊은 복사: 실제 데이터까지 복사 (안전!)

이동 의미론 (Move Semantics)

// 🔄 소유권 이동으로 불필요한 복사 방지 Vector(Vector&& other) // && = rvalue reference
C++
복사

학습 목표 달성도

완성된 기능들:
동적 메모리 관리
템플릿 활용
연산자 오버로딩
복사/이동 생성자
STL 호환 인터페이스
이 구현을 통해 배운 것:
메모리 관리: 동적 할당의 실제 활용
성능 최적화: 용량 증가 전략
C++ 고급 기능: 이동 의미론, 템플릿
라이브러리 설계: 사용자 친화적 인터페이스

다음 단계

추가로 구현해볼 만한 기능들:
insert(): 특정 위치에 요소 삽입
erase(): 특정 위치 요소 제거
reserve(): 용량 미리 확보
shrink_to_fit(): 불필요한 용량 제거
반복자 클래스: 더 정교한 반복자 구현
이렇게 완전한 vector 구현을 통해 C++ STL의 내부 동작을 이해하고, 실제 라이브러리가 어떻게 설계되는지 경험할 수 있습니다!