본문으로 건너뛰기 C++ Copy Algorithms: std::copy, copy_if, copy_n,

C++ Copy Algorithms: std::copy, copy_if, copy_n,

C++ Copy Algorithms: std::copy, copy_if, copy_n,

이 글의 핵심

std::copy and its variants let you transfer ranges between containers with clear intent and no boilerplate. This guide covers the full family — copy, copy_if, copy_n, copy_backward, and move — with working examples and the overlap gotcha.

Why Use STL Copy Algorithms?

Hand-written copy loops work, but STL algorithms express intent more clearly and let the compiler optimize better (trivially copyable types often compile to memmove):

// Hand loop — verbose, easy to get range wrong
for (size_t i = 0; i < src.size(); ++i) {
    dst[i] = src[i];
}

// STL — clear intent, range-safe
std::copy(src.begin(), src.end(), dst.begin());

// With growth — no manual resize needed
std::copy(src.begin(), src.end(), std::back_inserter(dst));

The copy algorithm family in <algorithm>:

AlgorithmCopiesSelection
std::copyFull range [first, last)All elements
std::copy_ifFull rangeElements where predicate is true
std::copy_nFirst n elementsCount-based
std::copy_backwardFull range (right-to-left)All elements
std::moveFull range (moves)All elements, sources become moved-from

std::copy

The most common: copy every element from source to destination.

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};

    // Destination pre-sized
    std::vector<int> dst1(src.size());
    std::copy(src.begin(), src.end(), dst1.begin());
    // dst1: {1, 2, 3, 4, 5}

    // Destination grows automatically
    std::vector<int> dst2;
    std::copy(src.begin(), src.end(), std::back_inserter(dst2));
    // dst2: {1, 2, 3, 4, 5}

    // Copy to array
    int arr[5];
    std::copy(src.begin(), src.end(), arr);

    // Copy to output stream
    std::copy(src.begin(), src.end(),
        std::ostream_iterator<int>(std::cout, " "));
    // prints: 1 2 3 4 5
}

Return value: iterator to one past the last element written in the destination. Useful if you need to know where copying ended.


std::copy_if

Copy only elements that satisfy a predicate:

#include <algorithm>
#include <vector>

int main() {
    std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::vector<int> evens;

    // Copy only even numbers
    std::copy_if(nums.begin(), nums.end(),
        std::back_inserter(evens),
        [](int n) { return n % 2 == 0; });
    // evens: {2, 4, 6, 8, 10}

    // Copy strings longer than 3 characters
    std::vector<std::string> words = {"hi", "hello", "hey", "greetings"};
    std::vector<std::string> long_words;

    std::copy_if(words.begin(), words.end(),
        std::back_inserter(long_words),
        [](const std::string& s) { return s.length() > 3; });
    // long_words: {"hello", "greetings"}
}

copy_if is the copy version of the filter operation. The destination must either be pre-sized (worst case: same size as source) or use back_inserter.


std::copy_n

Copy a fixed number of elements:

#include <algorithm>
#include <vector>
#include <list>

int main() {
    std::vector<int> src = {10, 20, 30, 40, 50};

    // Copy first 3 elements
    std::vector<int> dst(3);
    std::copy_n(src.begin(), 3, dst.begin());
    // dst: {10, 20, 30}

    // From a stream or generator (forward iterator)
    std::list<int> data = {1, 2, 3, 4, 5};
    std::vector<int> first3;
    std::copy_n(data.begin(), 3, std::back_inserter(first3));
    // first3: {1, 2, 3}
}

Warning: if n exceeds the number of readable elements, behavior is undefined. Validate before calling on streams or untrusted sizes.


std::copy_backward — Overlapping Ranges

This is the most confusing variant, but it solves a real problem: shifting elements right within the same buffer.

Why std::copy fails for rightward shifts:

src: [1][2][3][4][5]
dst starts at position 2 (shift right by 2)

copy goes left-to-right:
step 1: pos[2] = pos[0] → [1][2][1][4][5]  // dest overwrites src[2]
step 2: pos[3] = pos[1] → [1][2][1][2][5]  // dest overwrites src[3]
step 3: pos[4] = pos[2] → [1][2][1][2][1]  // reads OVERWRITTEN data — wrong!

copy_backward goes right-to-left, reading source before destination overwrites it:

#include <algorithm>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5};

    // Shift elements right by 2 positions (insert gap at beginning)
    // copy_backward(first, last, dest_last) — dest_last points one past destination end
    std::copy_backward(v.begin(), v.begin() + 3, v.begin() + 5);
    // Before: 1 2 3 4 5
    // After:  1 2 1 2 3  (first two positions now available for new data)

    // Example: insert at front by shifting
    std::vector<int> buf = {1, 2, 3, 4, 5};
    buf.push_back(0);  // make room
    std::copy_backward(buf.begin(), buf.begin() + 5, buf.end());
    buf[0] = 99;  // insert
    // buf: {99, 1, 2, 3, 4, 5}
}

Rule: when source and destination overlap and destination is to the right of source, use copy_backward. When destination is to the left (shifting left), copy is safe.


std::move (Algorithm)

std::move the algorithm (not the cast) transfers elements using move semantics, leaving sources in a valid-but-unspecified state:

#include <algorithm>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> src = {"hello", "world", "foo"};
    std::vector<std::string> dst;

    // Move strings — no copying of string data
    std::move(src.begin(), src.end(), std::back_inserter(dst));

    // src strings are now moved-from (valid but unspecified)
    // Safe to reassign or let them be destroyed
    for (auto& s : src) {
        s = "cleared";  // fine — reassigning moved-from
    }

    // dst has the original values
    // dst: {"hello", "world", "foo"}
}

Use move (algorithm) over copy when:

  • The source container will be discarded or reset after the operation
  • Elements are large (strings, vectors, unique_ptrs) and copying is expensive
  • Elements are move-only (unique_ptr, file handles)

Output Iterator Guide

The output iterator controls where elements go:

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <set>

std::vector<int> src = {1, 2, 3, 4, 5};

// Append to end of vector/string/deque
std::vector<int> dst1;
std::copy(src.begin(), src.end(), std::back_inserter(dst1));

// Prepend to front of list/deque (not vector — no push_front)
std::list<int> dst2;
std::copy(src.begin(), src.end(), std::front_inserter(dst2));
// dst2: {5, 4, 3, 2, 1} — reversed, because each element is prepended

// Insert at arbitrary position
std::vector<int> dst3 = {10, 20, 30};
std::copy(src.begin(), src.end(), std::inserter(dst3, dst3.begin() + 1));
// dst3: {10, 1, 2, 3, 4, 5, 20, 30}

// Write to output stream
std::copy(src.begin(), src.end(),
    std::ostream_iterator<int>(std::cout, ", "));
// prints: 1, 2, 3, 4, 5,

Performance Notes

For trivially copyable types (int, float, plain structs), the standard library implementation may optimize std::copy to a memmove call — equivalent to manually calling memcpy but safe for overlapping ranges.

Never use memcpy on non-trivial types like std::string, std::vector, or any class with a non-trivial copy constructor — it skips constructors and causes double-free or memory leaks.

// Safe — compiler knows int is trivially copyable
std::vector<int> a = {1, 2, 3, 4, 5};
std::vector<int> b(5);
std::copy(a.begin(), a.end(), b.begin());  // may compile to memmove

// Never do this for non-trivial types
std::vector<std::string> strs = {"hello", "world"};
std::vector<std::string> dst(2);
// memcpy(&dst[0], &strs[0], 2 * sizeof(std::string));  // WRONG — UB
std::copy(strs.begin(), strs.end(), dst.begin());  // correct

Common Mistakes

1. Destination too small:

std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(3);  // only room for 3
std::copy(src.begin(), src.end(), dst.begin());  // UB: writes past end
// Fix: resize dst first, or use back_inserter

2. Using copy for rightward overlap:

std::vector<int> v = {1, 2, 3, 4, 5};
std::copy(v.begin(), v.begin() + 3, v.begin() + 2);  // UB: overlapping, shifts right
std::copy_backward(v.begin(), v.begin() + 3, v.begin() + 5);  // correct

3. Reading moved-from elements:

std::move(src.begin(), src.end(), back_inserter(dst));
std::cout << src[0];  // UB or garbage — moved-from string
src.clear();  // safe — destroy or reassign

Key Takeaways

  • std::copy — copy all elements; use back_inserter to avoid pre-sizing the destination
  • std::copy_if — filter while copying with a predicate (the “filter” of the copy family)
  • std::copy_n — copy exactly n elements; validate n doesn’t exceed available elements
  • std::copy_backward — use when source and destination overlap with the destination to the right
  • std::move (algorithm) — transfer ownership instead of copying; sources become valid-but-unspecified
  • Never use memcpy on non-trivial types — use std::copy and let the compiler optimize

자주 묻는 질문 (FAQ)

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

A. Copy and move ranges safely in C++ with std::copy, copy_if, copy_n, copy_backward, and remove_copy. Covers output iterat… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

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

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

Q. 더 깊이 공부하려면?

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


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

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


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

C++, Algorithm, copy, move, STL 등으로 검색하시면 이 글이 도움이 됩니다.