C++ vector reserve vs resize | capacity vs size 완벽 비교

C++ vector reserve vs resize | capacity vs size 완벽 비교

이 글의 핵심

C++ vector reserve vs resize 차이점. reserve는 capacity만 늘리고 size는 그대로, resize는 길이·요소 초기화까지 바꿉니다. 재할당 줄이기 vs 미리 채우기, 성능과 사용 시점 가이드입니다.

들어가며

C++에서 vector의 reserveresize완전히 다른 동작을 합니다. reservecapacity만 늘리고, resizesize를 변경하고 요소를 초기화합니다.

비유로 말씀드리면, reserve창고 선반만 넓혀 두고 아직 상자는 안 올려 둔 상태, resize상자 개수 자체를 늘리거나 줄이고 내용물 자리까지 맞추는 것에 가깝습니다.

이 글을 읽으면

  • reserve vs resize의 차이를 명확히 이해합니다
  • size와 capacity의 관계를 파악합니다
  • 성능 비교와 최적화 전략을 익힙니다
  • 실무 시나리오별 선택 기준을 확인합니다

목차

  1. reserve vs resize 차이
  2. 실전 구현
  3. 고급 활용
  4. 성능 비교
  5. 실무 사례
  6. 트러블슈팅
  7. 마무리

reserve vs resize 차이

핵심 차이

항목reserve(n)resize(n)
size변경 없음n으로 변경
capacity최소 n최소 n
초기화없음기본값으로 초기화
인덱스 접근❌ (size 범위 내만)
push_back
재할당방지필요시 발생

시각적 비교

std::vector<int> vec;

// reserve: capacity만 늘림
vec.reserve(5);
// size: 0, capacity: 5
// [?, ?, ?, ?, ?]  (메모리만 확보, 요소 없음)

// resize: size 변경 + 초기화
vec.resize(5);
// size: 5, capacity: 5
// [0, 0, 0, 0, 0]  (요소 생성 + 초기화)

실전 구현

1) reserve: capacity만 늘림

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    vec.reserve(5);  // capacity를 5로 늘림
    
    std::cout << "size: " << vec.size() << std::endl;          // 0
    std::cout << "capacity: " << vec.capacity() << std::endl;  // 5
    
    // vec[0] = 42;  // ❌ 미정의 동작 (size가 0)
    
    vec.push_back(10);  // ✅ OK
    std::cout << "size: " << vec.size() << std::endl;  // 1
    
    return 0;
}

출력:

size: 0
capacity: 5
size: 1

2) resize: size 변경 + 초기화

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    vec.resize(5);  // size를 5로 늘리고 0으로 초기화
    
    std::cout << "size: " << vec.size() << std::endl;          // 5
    std::cout << "capacity: " << vec.capacity() << std::endl;  // 5 (최소)
    
    vec[0] = 42;  // ✅ OK
    std::cout << vec[0] << std::endl;  // 42
    std::cout << vec[1] << std::endl;  // 0 (초기화됨)
    
    return 0;
}

출력:

size: 5
capacity: 5
42
0

3) resize: 초기값 설정

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    vec.resize(5, 42);  // size를 5로, 모두 42로 초기화
    
    for (int val : vec) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

출력:

42 42 42 42 42

4) push_back vs 인덱스 접근

#include <iostream>
#include <vector>

int main() {
    // reserve: push_back으로 추가
    std::vector<int> vec1;
    vec1.reserve(3);
    
    vec1.push_back(10);
    vec1.push_back(20);
    vec1.push_back(30);
    
    std::cout << "vec1 size: " << vec1.size() << std::endl;  // 3
    
    // resize: 인덱스로 직접 접근
    std::vector<int> vec2;
    vec2.resize(3);
    
    vec2[0] = 10;
    vec2[1] = 20;
    vec2[2] = 30;
    
    std::cout << "vec2 size: " << vec2.size() << std::endl;  // 3
    
    return 0;
}

고급 활용

1) 재할당 횟수 측정

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    
    int realloc_count = 0;
    size_t last_capacity = vec.capacity();
    
    for (int i = 0; i < 1000; ++i) {
        vec.push_back(i);
        
        if (vec.capacity() != last_capacity) {
            ++realloc_count;
            std::cout << "재할당 " << realloc_count << ": "
                      << last_capacity << " → " << vec.capacity() << std::endl;
            last_capacity = vec.capacity();
        }
    }
    
    std::cout << "총 재할당 횟수: " << realloc_count << std::endl;
    
    return 0;
}

출력 예시:

재할당 1: 0 → 1
재할당 2: 1 → 2
재할당 3: 2 → 4
재할당 4: 4 → 8
...
총 재할당 횟수: 10

2) 2D 벡터 초기화

#include <iostream>
#include <vector>

int main() {
    // resize: 2D 벡터 초기화
    std::vector<std::vector<int>> matrix;
    matrix.resize(10);  // 10개 행
    
    for (auto& row : matrix) {
        row.resize(20, 0);  // 각 행을 20개 열로, 0으로 초기화
    }
    
    matrix[5][10] = 42;
    std::cout << matrix[5][10] << std::endl;  // 42
    
    return 0;
}

3) shrink_to_fit: capacity 줄이기

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    vec.resize(1000);
    
    std::cout << "size: " << vec.size() << std::endl;          // 1000
    std::cout << "capacity: " << vec.capacity() << std::endl;  // 1000
    
    vec.resize(10);  // size 줄임
    
    std::cout << "size: " << vec.size() << std::endl;          // 10
    std::cout << "capacity: " << vec.capacity() << std::endl;  // 1000 (그대로)
    
    vec.shrink_to_fit();  // capacity 줄임
    
    std::cout << "capacity: " << vec.capacity() << std::endl;  // 10
    
    return 0;
}

성능 비교

벤치마크: push_back

#include <chrono>
#include <iostream>
#include <vector>

void benchNoReserve() {
    std::vector<int> vec;
    for (int i = 0; i < 1000000; ++i) {
        vec.push_back(i);
    }
}

void benchReserve() {
    std::vector<int> vec;
    vec.reserve(1000000);  // 미리 공간 확보
    for (int i = 0; i < 1000000; ++i) {
        vec.push_back(i);
    }
}

void benchResize() {
    std::vector<int> vec;
    vec.resize(1000000);  // 초기화
    for (int i = 0; i < 1000000; ++i) {
        vec[i] = i;
    }
}

int main() {
    auto start1 = std::chrono::high_resolution_clock::now();
    benchNoReserve();
    auto end1 = std::chrono::high_resolution_clock::now();
    auto time1 = std::chrono::duration_cast<std::chrono::milliseconds>(end1 - start1).count();
    
    auto start2 = std::chrono::high_resolution_clock::now();
    benchReserve();
    auto end2 = std::chrono::high_resolution_clock::now();
    auto time2 = std::chrono::duration_cast<std::chrono::milliseconds>(end2 - start2).count();
    
    auto start3 = std::chrono::high_resolution_clock::now();
    benchResize();
    auto end3 = std::chrono::high_resolution_clock::now();
    auto time3 = std::chrono::duration_cast<std::chrono::milliseconds>(end3 - start3).count();
    
    std::cout << "reserve 없이: " << time1 << "ms" << std::endl;
    std::cout << "reserve 사용: " << time2 << "ms" << std::endl;
    std::cout << "resize 사용: " << time3 << "ms" << std::endl;
    
    return 0;
}

결과 (GCC 13, -O3):

방법시간상대 속도재할당
reserve 없이50ms6.25x20회
reserve 사용10ms1.25x0회
resize 사용8ms1.0x0회

분석:

  • reserve 없이: 재할당이 빈번해 느림
  • reserve 사용: 재할당 방지로 5배 빠름
  • resize 사용: 초기화 후 직접 접근으로 가장 빠름

실무 사례

사례 1: 파일 읽기

#include <fstream>
#include <iostream>
#include <string>
#include <vector>

std::vector<std::string> readLines(const std::string& filename) {
    std::vector<std::string> lines;
    lines.reserve(1000);  // 예상 크기
    
    std::ifstream file(filename);
    std::string line;
    
    while (std::getline(file, line)) {
        lines.push_back(line);
    }
    
    return lines;
}

int main() {
    auto lines = readLines("data.txt");
    std::cout << "읽은 줄 수: " << lines.size() << std::endl;
    
    return 0;
}

사례 2: 동적 프로그래밍 - DP 테이블

#include <iostream>
#include <vector>

int fibonacci(int n) {
    if (n <= 1) return n;
    
    std::vector<int> dp;
    dp.resize(n + 1);  // 크기 확정, 0으로 초기화
    
    dp[0] = 0;
    dp[1] = 1;
    
    for (int i = 2; i <= n; ++i) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    
    return dp[n];
}

int main() {
    std::cout << fibonacci(10) << std::endl;  // 55
    
    return 0;
}

사례 3: 그래프 인접 리스트

#include <iostream>
#include <vector>

class Graph {
private:
    std::vector<std::vector<int>> adj_;
    
public:
    Graph(int n) {
        adj_.resize(n);  // n개 노드
    }
    
    void addEdge(int u, int v) {
        adj_[u].push_back(v);
        adj_[v].push_back(u);
    }
    
    void print() const {
        for (int i = 0; i < adj_.size(); ++i) {
            std::cout << i << ": ";
            for (int neighbor : adj_[i]) {
                std::cout << neighbor << " ";
            }
            std::cout << std::endl;
        }
    }
};

int main() {
    Graph g(5);
    
    g.addEdge(0, 1);
    g.addEdge(0, 4);
    g.addEdge(1, 2);
    g.addEdge(1, 3);
    g.addEdge(1, 4);
    g.addEdge(2, 3);
    g.addEdge(3, 4);
    
    g.print();
    
    return 0;
}

사례 4: 이미지 처리 - 픽셀 버퍼

#include <iostream>
#include <vector>

struct Pixel {
    unsigned char r, g, b;
};

class Image {
private:
    int width_;
    int height_;
    std::vector<Pixel> pixels_;
    
public:
    Image(int width, int height) : width_(width), height_(height) {
        pixels_.resize(width * height, {0, 0, 0});  // 검은색으로 초기화
    }
    
    Pixel& at(int x, int y) {
        return pixels_[y * width_ + x];
    }
    
    void fill(Pixel color) {
        for (auto& pixel : pixels_) {
            pixel = color;
        }
    }
};

int main() {
    Image img(800, 600);
    
    img.at(100, 100) = {255, 0, 0};  // 빨간색 픽셀
    
    img.fill({255, 255, 255});  // 흰색으로 채우기
    
    return 0;
}

트러블슈팅

문제 1: reserve 후 인덱스 접근

증상: 미정의 동작, 크래시

// ❌ 잘못된 사용
std::vector<int> vec;
vec.reserve(10);

vec[0] = 42;  // ❌ 미정의 동작 (size가 0)

// ✅ 올바른 사용 1: push_back
std::vector<int> vec1;
vec1.reserve(10);
vec1.push_back(42);  // ✅ OK

// ✅ 올바른 사용 2: resize
std::vector<int> vec2;
vec2.resize(10);
vec2[0] = 42;  // ✅ OK

문제 2: resize 후 push_back

증상: 의도와 다른 크기

// ❌ 혼동하기 쉬운 패턴
std::vector<int> vec;
vec.resize(10);  // size: 10

vec.push_back(42);  // size: 11 (늘어남)

std::cout << vec.size() << std::endl;  // 11
std::cout << vec[10] << std::endl;     // 42

// ✅ 의도한 동작
std::vector<int> vec2;
vec2.resize(10);

vec2[0] = 42;  // size: 10 (그대로)

문제 3: capacity가 줄어들지 않음

증상: 메모리 낭비

// ❌ resize로 줄여도 capacity는 그대로
std::vector<int> vec;
vec.resize(1000);

std::cout << "size: " << vec.size() << std::endl;          // 1000
std::cout << "capacity: " << vec.capacity() << std::endl;  // 1000

vec.resize(10);  // size만 줄임

std::cout << "size: " << vec.size() << std::endl;          // 10
std::cout << "capacity: " << vec.capacity() << std::endl;  // 1000 (그대로)

// ✅ shrink_to_fit으로 capacity 줄이기
vec.shrink_to_fit();

std::cout << "capacity: " << vec.capacity() << std::endl;  // 10

문제 4: 불필요한 초기화

증상: 성능 저하

// ❌ resize로 초기화 후 다시 채우기 (비효율)
std::vector<int> vec;
vec.resize(1000);  // 0으로 초기화

for (int i = 0; i < 1000; ++i) {
    vec[i] = i;  // 다시 채우기
}

// ✅ reserve로 재할당만 방지
std::vector<int> vec2;
vec2.reserve(1000);

for (int i = 0; i < 1000; ++i) {
    vec2.push_back(i);  // 초기화 없이 바로 추가
}

마무리

reserve재할당 방지, resize크기 변경 + 초기화입니다.

핵심 요약

  1. reserve vs resize

    • reserve: capacity만 늘림, size 그대로
    • resize: size 변경 + 초기화
  2. 선택 기준

    • push_back 사용: reserve
    • 인덱스 접근: resize
    • 초기화 필요: resize
    • 재할당 방지만: reserve
  3. 성능

    • reserve 없이: 재할당 빈번 (느림)
    • reserve 사용: 재할당 방지 (5배 빠름)
    • resize 사용: 직접 접근 (가장 빠름)
  4. 주의사항

    • reserve 후 인덱스 접근 금지
    • resize 후 push_back은 size 증가
    • capacity는 줄어들지 않음 (shrink_to_fit 필요)

선택 가이드

상황사용이유
push_back으로 추가reserve재할당 방지
인덱스로 직접 접근resizesize 확보
크기를 모름reserve예상 크기
크기를 알고 초기화resize초기값 설정
재할당 방지만reserve성능
요소 초기화 필요resize기본값

코드 예제 치트시트

// reserve: capacity만 늘림
std::vector<int> vec1;
vec1.reserve(100);
for (int i = 0; i < 100; ++i) {
    vec1.push_back(i);
}

// resize: size 변경 + 초기화
std::vector<int> vec2;
vec2.resize(100);
for (int i = 0; i < 100; ++i) {
    vec2[i] = i;
}

// resize: 초기값 설정
std::vector<int> vec3;
vec3.resize(100, 42);

// shrink_to_fit: capacity 줄이기
vec3.resize(10);
vec3.shrink_to_fit();

다음 단계

  • vector 기초: C++ vector 기초
  • 컨테이너 비교: C++ vector vs list vs deque
  • 성능 최적화: C++ 성능 최적화

참고 자료

한 줄 정리: push_back을 쓴다면 reserve로 재할당을 방지하고, 인덱스 접근이 필요하면 resize로 크기를 확정한다.