C++ 벡터 컨테이너 (std::vector)

한눈에 보기

연속 메모리 기반 동적 배열인 std::vector의 size와 capacity, 재할당과 분할 상환 O(1) push_back, 반복자 무효화, reserve와 emplace_back 활용법을 정리한다.

최종 업데이트 2026.09.17

std::vector는 C++ 표준 라이브러리가 제공하는 동적 배열(Dynamic Array) 컨테이너로, 원소를 힙(Heap)의 연속된 메모리에 저장하면서 크기가 자동으로 늘어난다. 인덱스 접근이 O(1)이고 끝에 추가하는 push_back이 분할 상환(Amortized) O(1)이라는 성능 특성을 가지며, 크기(size)와 용량(capacity)의 구분, 재할당(Reallocation)과 반복자 무효화(Iterator Invalidation)를 이해하는 것이 활용의 핵심이다.

내부 구조: size와 capacity

vector는 개념적으로 세 가지 정보를 가진다. 힙에 할당한 배열의 시작 주소, 현재 담긴 원소 개수인 size, 그리고 재할당 없이 담을 수 있는 최대 개수인 capacity다. size가 capacity에 도달한 상태에서 push_back을 호출하면, 더 큰 새 배열을 할당하고 기존 원소를 모두 옮긴 뒤 옛 배열을 해제하는 재할당이 일어난다. 이때 성장 배율은 구현마다 다르지만 보통 1.5배나 2배씩 늘린다. 매번 1칸씩 늘리면 추가마다 전체 복사가 필요해 O(n)이 되지만, 배수로 늘리면 재할당 비용이 여러 번의 추가에 분산되어 push_back의 평균 비용이 O(1)이 된다. 이것이 분할 상환 분석의 대표 사례다.

#include <iostream>
#include <vector>

int main() {
    std::vector<int> v;
    for (int i = 0; i < 10; i++) {
        v.push_back(i);
        std::cout << v.size() << " / " << v.capacity() << '\n';
    }
    /* size는 1씩 늘지만 capacity는 1, 2, 4, 8, 16처럼 배수로 는다 */
    return 0;
}

최종 원소 개수를 미리 안다면 reserve(n)으로 용량을 확보해 재할당과 복사를 아예 없앨 수 있다. resize(n)은 용량이 아니라 size 자체를 바꿔 원소를 실제로 만들거나 지운다는 점에서 reserve와 다르다. 원소를 지워도 capacity는 줄지 않으므로, 메모리를 돌려주고 싶으면 shrink_to_fit을 호출하거나 빈 vector와 swap하는 관용구를 쓴다.

주요 연산과 시간 복잡도

연산복잡도비고
인덱스 접근 v[i], at(i)O(1)at은 범위 검사 후 예외 발생
push_back, pop_back분할 상환 O(1)재할당 시에만 O(n)
중간 insert, eraseO(n)뒤쪽 원소를 모두 이동
탐색(정렬 안 된 상태)O(n)정렬 후 이분 탐색은 O(log n)

연속 메모리라는 특성 덕분에 vector는 캐시 지역성(Cache Locality)이 뛰어나다. 순회할 때 인접 원소가 이미 캐시에 올라와 있으므로, 노드가 힙에 흩어져 있는 std::list보다 실제 순회 속도가 훨씬 빠르다. 중간 삽입과 삭제가 이론상 O(1)인 list가 실측에서는 vector에 지는 경우가 많은 이유이기도 하다. 이런 이유로 모던 C++(Modern C++) 진영에서는 특별한 근거가 없는 한 기본 컨테이너로 vector를 쓰라고 권장한다.

반복자 무효화

재할당이 일어나면 원소들이 새 메모리로 이동하므로, 이전 배열을 가리키던 모든 반복자(Iterator), 포인터, 참조가 무효가 된다. 재할당이 없더라도 중간 erase나 insert는 해당 위치 뒤쪽의 반복자를 무효화한다. 반복문을 돌면서 원소를 지울 때 흔히 실수하는 지점이며, erase가 반환하는 다음 위치 반복자를 받아서 진행하는 것이 올바른 패턴이다.

std::vector<int> v = {1, 2, 3, 4, 5, 6};
for (auto it = v.begin(); it != v.end(); ) {
    if (*it % 2 == 0) {
        it = v.erase(it);   /* 지운 다음 위치를 돌려받는다 */
    } else {
        ++it;
    }
}
/* C++20부터는 std::erase_if(v, 조건)로 한 줄에 처리할 수 있다 */

사용할 때 알아 두면 좋은 것들

emplace_back은 인자를 받아 vector 내부 공간에서 원소를 직접 생성하므로, 임시 객체를 만들어 넣는 push_back보다 복사나 이동을 줄일 수 있다. 원소 타입의 이동 생성자가 noexcept로 선언되어 있으면 재할당 시 복사 대신 이동이 사용되어 성능이 좋아진다는 점도 모던 C++에서 중요한 포인트다. vector가 스스로 힙 메모리를 할당하고 소멸 시 해제하는 것은 자원 획득은 초기화(RAII, Resource Acquisition Is Initialization) 원칙의 전형이므로, new로 만든 동적 배열을 직접 delete하는 코드 대부분은 vector로 대체하는 것이 안전하다. 한 가지 예외적인 주의 사항으로 vector<bool>은 공간 절약을 위해 비트 단위로 압축된 특수화라서 원소의 실제 참조를 얻을 수 없으므로, 불리언 배열이 필요하면 std::deque<bool>이나 std::vector<char>를 고려한다.

면접에서 이렇게 물어본다

  • vector의 size와 capacity의 차이는 무엇이며, capacity는 언제 어떻게 늘어나는가?
  • push_back이 분할 상환 O(1)인 이유를 재할당 전략과 함께 설명해 보라.
  • 반복자 무효화란 무엇이고, 순회 중 원소를 삭제할 때 어떻게 처리해야 하는가?
  • vector와 list의 차이를 캐시 지역성 관점까지 포함해 비교해 보라.
  • reserve와 resize의 차이, 그리고 push_back과 emplace_back의 차이는 무엇인가?
C / C++ 문서 전체 보기

이 개념, 내 코드에서는 어떻게 물어볼까요

데브핏(DevFit)에 채용공고 URL을 붙여 넣고 레포지토리를 연결하면, 내가 쓴 커밋에서 예상 질문과 어필 포인트를 만듭니다. 하루 10회까지 무료입니다. 합불 판정이나 지원자 랭킹은 만들지 않고, 최종 판단은 사람이 합니다.

공고 넣고 시작하기