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>:
| Algorithm | Copies | Selection |
|---|---|---|
std::copy | Full range [first, last) | All elements |
std::copy_if | Full range | Elements where predicate is true |
std::copy_n | First n elements | Count-based |
std::copy_backward | Full range (right-to-left) | All elements |
std::move | Full 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; useback_inserterto avoid pre-sizing the destinationstd::copy_if— filter while copying with a predicate (the “filter” of the copy family)std::copy_n— copy exactlynelements; validatendoesn’t exceed available elementsstd::copy_backward— use when source and destination overlap with the destination to the rightstd::move(algorithm) — transfer ownership instead of copying; sources become valid-but-unspecified- Never use
memcpyon non-trivial types — usestd::copyand 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 Replace | ‘치환 알고리즘’ 가이드
- C++ Algorithm Reverse | reverse·rotate·reverse_copy 완벽 정리
- C++ Algorithm Remove | ‘제거 알고리즘’ 가이드
이 글에서 다루는 키워드 (관련 검색어)
C++, Algorithm, copy, move, STL 등으로 검색하시면 이 글이 도움이 됩니다.