나만의 작은 도서관

[C++][STL Algorithm] vector 부분 복사에 유용한 std::copy(feat. inserter 계열 클래스) 본문

C++/문법 및 메소드(STL)

[C++][STL Algorithm] vector 부분 복사에 유용한 std::copy(feat. inserter 계열 클래스)

pledge24 2026. 1. 9. 18:31

std::copy란?

template <class InputIt, class OutputIt>
OutputIt copy(InputIt first, InputIt last, OutputIt d_first);
  • std::copy란 지정한 범위의 메모리를 복사하여 다른 위치에 붙여 넣는 함수로, 3개의 iterator를 인자로 받으며 [first, last) 범위의 메모리를 복사하여 d_first부터 붙여 넣는다.
  • 다른 STL Algorithm 함수들과 동일하게 last 위치는 범위에 포함시키지 않기 때문에 std::copy의 last 위치의 메모리가 복사 범위에 들어가지 않음을 유의해야 한다.
  • std::copy를 사용할 수 있는 범위가 굉장히 넓지만, 제목에서 알 수 있듯 자주 사용되는 상황은 vector 부분 복사이다. 예제는 아래와 같다.
vector<int> v1 = {1, 2, 3, 4, 5};

int first = 1;
int last = 3;

vector<int> v2(last-first+1);
std::copy(v1.begin()+first, v1.begin()+last+1, v2.begin());

for(const int& elem : v2)
{
	cout << elem << ' ';
}
cout << '\\n';

// 실행 결과
2 3 4

std::copy는 반복자(iterator) 기능이 있는 모든 클래스에서 사용 가능하다.

  • std::copy는 컨테이너에 종속된 함수가 아니라 iterator를 중점으로 로직이 돌아가기 때문에 iterator 기능을 넣어놓은 클래스라면 사용이 가능하다.
  • STL의 컨테이너(단, queue, stack과 같은 컨테이너 어댑터는 제외)들은 iterator를 지원하기 때문에 전부 사용이 가능하다. 나열하면 아래와 같다.
    • 시퀀스 컨테이너: std::vector, std::list, std::deque, std::array, std::forward_list 등.
    • 연관/무순서 컨테이너: std::set, std::map, std::unordered_set 등 (주로 이 컨테이너들의 데이터를 다른 곳으로 복사할 때 사용).
    • C 스타일 배열: 일반 배열의 포인터도 반복자의 역할을 하므로 사용 가능.
    • 기타: std::string, std::valarray 등.

for문 방식과 비교했을 때의 장점

// 1. 기본 방식
{
    vector<int> v2;
    for(int i = first; i <= last; i++)
    {
        int num = v1[i];
        v2.push_back(num);
    }
}

// 2. std::copy() 사용 방식
{
    vector<int> v2(last-first+1);
    std::copy(v1.begin()+first, v1.begin()+last+1, v2.begin());
}
  • 1번 방식과 2번 방식 둘 다 v1의 first부터 last까지의 원소를 v2에 복사하는 코드이다. 1번 방식인 for문 활용 코드도 코드 길이가 길지 않기 때문에 충분히 좋은 방식이지만, 2번 방식인 std::copy() 활용 코드가 한 줄의 코드로 복사를 하는 코드임을 명시할 수 있기 때문에 1번 방식보다 가독성이 높고 코드 길이도 짧다.

주의 사항: 붙여 넣을 위치에 메모리가 부족하면 오류 발생!

{
    vector<int> v2;
    std::copy(v1.begin()+first, v1.begin()+last+1, v2.begin());
}
  • std::copy는 복사 붙여 넣기를 할 뿐, 붙여 넣을 위치에 메모리가 부족했을 때 알아서 메모리를 확보하지는 않는다. 그렇기 때문에 위 코드처럼 메모리를 충분히 확보하지 않은 벡터 v2의 begin()을 그대로 3번째 인자에 넣게 되면 오류가 발생하게 된다.
  • 그렇다면 매번 std::copy를 쓸 때마다 붙여 넣을 위치에 충분한 메모리가 확보되었는지 확인해야 할까? 이러한 확인 작업은 매우 귀찮기도 하고, 놓치기도 쉽기 때문에 최대한 안 하고 싶을 텐데, 이럴 때는 알아서 메모리 공간을 확보하는 inserter 계열의 클래스를 같이 사용하면 된다.
// 2-1. std::copy() + back_inserter 사용 방식
// back_inserter를 인자로 넘겨주면 미리 메모리 확보를 하지 않아도 된다!
{
    vector<int> v2;
    std::copy(v1.begin()+first, v1.begin()+last+1, back_inserter(v2));
}

 

 

inserter 계열의 클래스?

// std::back_inserter
template<class Container>
std::back_insert_iterator<Container> back_inserter(Container& c)
{
    return std::back_insert_iterator<Container>(c);
}

// std::front_inserter
template<class Container>
std::front_insert_iterator<Container> front_inserter( Container& c )
{
    return std::front_insert_iterator<Container>(c);
}

// std::inserter
template<class Container>
std::insert_iterator<Container> inserter(Container& c, typename Container::iterator i)
{
    return std::insert_iterator<Container>(c, i);
}
  • inserter 계열의 클래스는 컨테이너에 새로운 원소를 삽입할 때 사용하는 특수한 출력 반복자 어댑터(Output Iterator Adapter)로, 3가지 종류가 있으며, 각각 아래와 같다.
    1. std::back_inserter
      • 컨테이너 맨 뒤에 원소를 삽입한다.
      • 내부에서 push_back(value)을 호출하기 때문에, push_back 멤버 함수가 존재하는 컨테이너만 사용가능하다.
      • 사용 가능 컨테이너: std::vector, std::deque, std::list, std::string 등
    2. std::front_inserter
      • 컨테이너 맨 앞에 원소를 삽입한다.
      • 내부에서 push_front(value)를 호출하기 때문에, push_front 멤버 함수가 존재하는 컨테이너만 사용가능하다.
      • 사용 가능 컨테이너: std::deque, std::list, std::forward_list 등
    3. std::inserter(일반 삽입자)
      • 컨테이너의 특정 위치에 원소를 삽입한다.
      • 내부에서 insert(it, value)를 호출하기 때문에, insert 멤버 함수가 존재하는 컨테이너만 사용가능하다.
      • 사용 가능 컨테이너: 거의 모든 STL 컨테이너
      • 특징: 연관 컨테이너(set, map)의 경우, 삽입 위치 인자인 it을 힌트로만 사용하며 실제로는 정렬된 위치에 삽입된다.

조건 복사 std::copy_if

template <class InputIt, class OutputIt, class UnaryPredicate>
OutputIt copy_if(InputIt first, InputIt last, OutputIt d_first,
                 UnaryPredicate pred);
  • 복사를 하되, 조건에 맞는 요소만 복사하고 싶다면 std::copy_if를 사용하면 된다. 4개의 인자를 전달해야 하며, 앞에 3개는 std::copy와 동일하게 넣은 다음 마지막 인자에 “단항 서술자(UnaryPredicate)”를 넣어주면 된다. 예제는 아래와 같다.
vector<int> v2;
std::copy_if(v1.begin(), v1.end(), back_inserter(v2), [](const int& elem){
    // 짝수만 복사
    return elem % 2 == 0;
});

for(const int& elem : v2)
{
	cout << elem << ' ';
}
cout << '\\n';

// 실행 결과
2 4

 

단항 서술자(UnaryPredicate)?

  • STL 알고리즘에 인자로 Predicate 타입이 자주 나오는데, C++에서 Predicate는 반환 타입이 bool인 함수로, Predicate 계열인 단항 서술자는 인자가 한 개인 Predicate를 의미한다. 즉, 단항 서술자는 “인자가 한 개이고, bool 타입을 반환하는 functor”를 의미한다.