본문으로 건너뛰기 C++ vector reserve vs resize | capacity vs size 완벽 비교

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의 관계를 파악합니다
  • 성능 비교와 최적화 전략을 익힙니다
  • 실무 시나리오별 선택 기준을 확인합니다

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

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();

다음 단계

참고 자료

  • “Effective STL” - Scott Meyers
  • “C++ Primer” - Stanley Lippman
  • cppreference: https://en.cppreference.com/w/cpp/container/vector 한 줄 정리: push_back을 쓴다면 reserve로 재할당을 방지하며, 인덱스 접근이 필요하면 resize로 크기를 확정한다.

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. C++ vector reserve vs resize 비교. reserve는 capacity만 늘리고 size는 그대로, resize는 길이·요소 초기화까지 바꿉니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, vector, reserve, resize, 메모리, 성능최적화, STL 등으로 검색하시면 이 글이 도움이 됩니다.