C++ vector reserve vs resize | capacity vs size 완벽 비교
이 글의 핵심
C++ vector reserve vs resize 차이점. reserve는 capacity만 늘리고 size는 그대로, resize는 길이·요소 초기화까지 바꿉니다. 재할당 줄이기 vs 미리 채우기, 성능과 사용 시점 가이드입니다.
들어가며
C++에서 vector의 reserve와 resize는 완전히 다른 동작을 합니다. reserve는 capacity만 늘리고, resize는 size를 변경하고 요소를 초기화합니다.
비유로 말씀드리면, 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
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 없이 | 50ms | 6.25x | 20회 |
| reserve 사용 | 10ms | 1.25x | 0회 |
| resize 사용 | 8ms | 1.0x | 0회 |
분석:
- 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는 크기 변경 + 초기화입니다.
핵심 요약
-
reserve vs resize
- reserve: capacity만 늘림, size 그대로
- resize: size 변경 + 초기화
-
선택 기준
- push_back 사용: reserve
- 인덱스 접근: resize
- 초기화 필요: resize
- 재할당 방지만: reserve
-
성능
- reserve 없이: 재할당 빈번 (느림)
- reserve 사용: 재할당 방지 (5배 빠름)
- resize 사용: 직접 접근 (가장 빠름)
-
주의사항
- reserve 후 인덱스 접근 금지
- resize 후 push_back은 size 증가
- capacity는 줄어들지 않음 (shrink_to_fit 필요)
선택 가이드
| 상황 | 사용 | 이유 |
|---|---|---|
| push_back으로 추가 | reserve | 재할당 방지 |
| 인덱스로 직접 접근 | resize | size 확보 |
| 크기를 모름 | 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++ 성능 최적화
참고 자료
- “Effective STL” - Scott Meyers
- “C++ Primer” - Stanley Lippman
- cppreference: https://en.cppreference.com/w/cpp/container/vector
한 줄 정리: push_back을 쓴다면 reserve로 재할당을 방지하고, 인덱스 접근이 필요하면 resize로 크기를 확정한다.