#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의 내부 동작을 이해하고, 실제 라이브러리가 어떻게 설계되는지 경험할 수 있습니다! 