C++ Algorithm Copy | "복사 알고리즘" 가이드
이 글의 핵심
복사 알고리즘은 STL에서 제공하는 범위 기반 복사 유틸리티입니다. 원본 범위의 요소를 목적지로 복사하거나, 조건에 맞는 요소만 선택적으로 복사할 수 있습니다. 실무에서는 벡터 간 데이터 이동, 필터링된 결과 수집, 로그 데이터 복사 등에 자주 사용됩니다.
복사 알고리즘이란?
복사 알고리즘은 STL에서 제공하는 범위 기반 복사 유틸리티입니다. 원본 범위의 요소를 목적지로 복사하거나, 조건에 맞는 요소만 선택적으로 복사할 수 있습니다. 실무에서는 벡터 간 데이터 이동, 필터링된 결과 수집, 로그 데이터 복사 등에 자주 사용됩니다.
왜 필요한가?
문제: 수동으로 루프를 돌려 복사하면 코드가 길어지고 실수하기 쉽습니다.
// 수동 복사 (번거로움)
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
for (size_t i = 0; i < src.size(); ++i) {
dst.push_back(src[i]);
}
해결: std::copy 같은 알고리즘으로 한 줄로 처리합니다.
// STL 복사 (간결함)
std::copy(src.begin(), src.end(), std::back_inserter(dst));
복사 알고리즘 종류
범위 복사에는 여러 변형이 있습니다:
- copy: 전체 범위 복사
- copy_if: 조건 만족 요소만 복사
- copy_n: 처음 N개만 복사
- move: 이동 (복사 회피)
- remove_copy: 특정 값 제외하고 복사
#include <algorithm>
#include <vector>
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(5);
// 전체 범위 복사
std::copy(src.begin(), src.end(), dst.begin());
핵심: 목적지 범위는 충분한 크기를 가져야 합니다. 크기가 부족하면 버퍼 오버플로우가 발생합니다.
copy vs copy_if vs copy_n
세 알고리즘은 모두 “한 방향으로 요소를 옮긴다”는 점은 같지만, 입력 범위를 어떻게 정의하느냐가 다릅니다.
| 알고리즘 | 입력 범위 | 선택 기준 | 목적지 크기 |
|---|---|---|---|
copy | [first, last) 전체 | 없음 (전부 복사) | last - first 이상 또는 back_inserter |
copy_if | [first, last) | predicate가 참인 것만 | 최대 last - first (실제는 조건에 따라 적음) |
copy_n | first부터 n개 | 없음 (개수만) | n 이상 (또는 back_inserter로 n번 대입) |
copy: 가장 기본입니다. “여기서 여기까지 그대로” 복제할 때 씁니다.copy_if: 필터링 복사입니다. 원본은 그대로 두고, 조건에 맞는 요소만 새 시퀀스로 모을 때remove_if로 지우기 전에 “보기만” 하고 싶을 때 유용합니다.copy_n: 끝 반복자가 없을 때 씁니다. 스트림 이터레이터, 생성기, “앞에서부터 정확히 n개만” 같은 패턴과 잘 맞습니다. 이때 유효한 입력이 n개 미만이면 UB이므로, 스트림이나 불확실한 소스면 먼저 길이를 확인하거나istream_iterator와 조합을 신중히 설계해야 합니다.
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> a, b, c;
std::copy(src.begin(), src.end(), std::back_inserter(a));
std::copy_if(src.begin(), src.end(), std::back_inserter(b),
[](int x) { return x % 2 == 0; });
std::copy_n(src.begin(), 3, std::back_inserter(c));
// a: 1..5, b: 짝수만, c: 1,2,3
copy_backward: 겹치는 범위에서의 안전한 복사
std::copy는 일반적으로 앞에서 뒤로 대입합니다. 원본과 목적지가 같은 버퍼 안에서 겹치고, 블록이 오른쪽(높은 주소) 으로 이동해야 하면, 아직 읽지 않은 원본이 덮어써져 UB가 될 수 있습니다. 이때 쓰는 것이 copy_backward입니다.
- 시그니처:
copy_backward(first, last, d_last)—d_last는 목적지 구간의 끝 다음을 가리킵니다(역방향 복사의 “끝 기준점”). - 언제: 같은
vector안에서 부분 구간을 오른쪽으로 밀거나, 겹치는 슬라이드가 오른쪽일 때.
std::vector<int> v = {1, 2, 3, 4, 5};
// v[0..2)를 한 칸 오른쪽으로: {1,1,2,3,5} 형태가 되려면 끝에서부터 복사
std::copy_backward(v.begin(), v.begin() + 3, v.begin() + 4);
// v: 1,1,2,3,5
move_backward는 같은 이유로 이동 시 겹침을 처리합니다. 왼쪽으로 당기는 경우에는 보통 copy/move가 안전합니다.
반복자 무효화 주의
복사 알고리즘 자체는 “값을 대입한다”는 점에서 단순하지만, 목적지 컨테이너의 반복자는 상황에 따라 무효화됩니다.
vector에insert/push_back/emplace로 끝에 붙이기: 재할당이 일어나면 모든 반복자·참조가 무효일 수 있습니다.reserve로 용량을 맞춰 두면 재할당을 줄일 수 있습니다.back_inserter: 내부적으로push_back을 반복 호출하므로, 미리reserve하지 않은 큰 복사에서는 반복자를 잡아 둔 채로 루프에 쓰면 위험합니다.deque의 중간 삽입 등은 컨테이너별 무효화 규칙을 확인해야 합니다.
실무에서는 “복사 중에 다른 스레드가 같은 vector를 쓰는가?” 같은 동시성도 반복자 무효화와 겹치면 데이터 레이스나 이중 해제로 이어질 수 있으므로, 공유 컨테이너에는 뮤텍스·불변 스냅샷 등 별도 설계가 필요합니다.
실전 예제: 벡터 복사와 필터링
동일 타입 벡터 간 복제
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
dst.reserve(src.size());
std::copy(src.begin(), src.end(), std::back_inserter(dst));
크기를 이미 알면 dst.resize(src.size()); std::copy(..., dst.begin()); 한 번이 push_back 오버헤드 없이 깔끔합니다.
조건에 맞는 요소만 새 벡터로
std::vector<int> scores = {55, 72, 40, 90, 61};
std::vector<int> pass;
std::copy_if(scores.begin(), scores.end(), std::back_inserter(pass),
[](int s) { return s >= 60; });
// pass: 72, 90, 61
원본 scores는 유지되므로, 같은 데이터로 “불합격만 보기” 같은 다른 copy_if도 이어서 만들 수 있습니다.
성능: std::copy와 memcpy의 관계
- 튜비얼리 복사 가능(trivially copyable)한 타입(예:
int,double,char배열)에 대해, 구현은 종종memmove/memcpy에 가까운 최적화를 할 수 있습니다. 그래도 표준 보장은 “복사 대입” 횟수이지, 반드시memcpy라는 뜻은 아닙니다. std::string,std::vector, 사용자 정의 클래스처럼 비트 단위 복사가 안 되는 타입에는memcpy를 쓰면 UB입니다. 반드시std::copy나 컨테이너의 복사/이동 생성자를 사용하세요.- 속도만 보려면: 연속 메모리의 POD/트리비얼 타입 대량 복사는
std::memcpy/std::memmove가 측정상 유리한 경우가 많지만, 유지보수와 이식성을 위해 먼저std::copy로 충분한지 프로파일링한 뒤, 병목이 증명된 핫패스에서만 저수준 최적화를 고려하는 편이 안전합니다. - C++20
std::ranges::copy등 범위 버전은 의도는 같고, 반복자 실수를 줄이는 데 도움이 됩니다.
copy: 기본 복사
std::copy는 범위 [first, last)의 모든 요소를 목적지로 복사합니다. 반환값은 복사된 마지막 요소의 다음 위치를 가리키는 반복자입니다.
#include <algorithm>
#include <vector>
#include <iostream>
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
// back_inserter 사용 (자동으로 push_back 호출)
std::copy(src.begin(), src.end(), std::back_inserter(dst));
// dst: {1, 2, 3, 4, 5}
for (int x : dst) {
std::cout << x << " "; // 1 2 3 4 5
}
동작 원리: back_inserter는 출력 반복자로, 대입 연산자가 호출될 때마다 push_back()을 실행합니다. 따라서 목적지 컨테이너의 크기를 미리 확보하지 않아도 자동으로 확장됩니다.
성능: 복사 생성자가 N번 호출됩니다. 큰 객체(예: std::string, 사용자 정의 클래스)는 복사 비용이 크므로, 이동이 가능하면 std::move를 고려하세요.
실전 예시
예시 1: copy_if - 조건부 복사
std::copy_if는 조건(predicate)을 만족하는 요소만 복사합니다. 필터링된 데이터를 수집할 때 유용합니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::vector<int> dst;
// 짝수만 복사
std::copy_if(src.begin(), src.end(), std::back_inserter(dst),
[](int x) { return x % 2 == 0; });
for (int x : dst) {
std::cout << x << " "; // 2 4 6 8 10
}
}
동작 원리: 각 요소에 대해 predicate(람다 함수)를 호출하고, true를 반환하는 요소만 목적지에 복사합니다. 원본은 변경되지 않습니다.
실무 활용:
- 로그에서 에러 레벨만 필터링
- 사용자 목록에서 활성 사용자만 추출
- 센서 데이터에서 임계값 초과 샘플만 수집
// 실무 예시: 에러 로그만 수집
struct LogEntry {
std::string level;
std::string message;
};
std::vector<LogEntry> logs = { /* ... */ };
std::vector<LogEntry> errors;
std::copy_if(logs.begin(), logs.end(), std::back_inserter(errors),
[](const LogEntry& e) { return e.level == "ERROR"; });
예시 2: copy_n - N개만 복사
std::copy_n은 처음 N개 요소만 복사합니다. 범위의 끝을 지정하지 않고 개수만 지정하므로, 스트림이나 무한 시퀀스에서 일부만 가져올 때 유용합니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst;
// 처음 3개만 복사
std::copy_n(src.begin(), 3, std::back_inserter(dst));
for (int x : dst) {
std::cout << x << " "; // 1 2 3
}
}
동작 원리: 시작 반복자에서 N번 전진하며 각 요소를 복사합니다. N이 범위를 초과하면 정의되지 않은 동작(UB)이므로, N이 유효한지 확인해야 합니다.
실무 활용:
- 파일에서 처음 100줄만 읽기
- 네트워크 패킷에서 헤더 크기만큼만 복사
- 대용량 데이터에서 샘플링
// 실무 예시: 파일 헤더만 읽기
std::ifstream file("data.bin", std::ios::binary);
std::vector<char> header(512);
std::copy_n(std::istreambuf_iterator<char>(file), 512, header.begin());
예시 3: move - 이동으로 성능 향상
std::move 알고리즘은 요소를 복사 대신 이동합니다. 큰 객체(예: std::string, std::vector)를 다룰 때 복사 비용을 크게 줄일 수 있습니다.
#include <algorithm>
#include <vector>
#include <string>
#include <iostream>
int main() {
std::vector<std::string> src = {"hello", "world", "C++"};
std::vector<std::string> dst;
// 이동 (복사 아님)
std::move(src.begin(), src.end(), std::back_inserter(dst));
for (const auto& s : dst) {
std::cout << s << " "; // hello world C++
}
std::cout << "\n";
// src는 유효하지만 지정되지 않은 상태 (보통 빈 문자열)
for (const auto& s : src) {
std::cout << "'" << s << "' "; // '' '' ''
}
}
동작 원리: 각 요소에 대해 이동 생성자를 호출합니다. std::string의 경우, 내부 버퍼 포인터만 이동하고 원본은 빈 상태가 됩니다. 복사는 O(N × 문자열 길이)이지만, 이동은 O(N × 포인터 크기)로 훨씬 빠릅니다.
주의: 이동 후 원본 컨테이너의 요소는 유효하지만 지정되지 않은 상태(valid but unspecified state) 입니다. 재할당하거나 clear() 후 재사용하세요.
성능 비교:
// 복사: 10,000개 문자열 × 평균 100바이트 = 약 1MB 복사
std::copy(src.begin(), src.end(), std::back_inserter(dst));
// 이동: 10,000개 포인터 × 8바이트 = 80KB 이동 (약 12배 빠름)
std::move(src.begin(), src.end(), std::back_inserter(dst));
실무 활용:
- 임시 벡터를 다른 컨테이너로 이동
- 파싱 결과를 캐시로 이동
- 큐에서 작업 항목을 워커로 이동
예시 4: remove_copy - 특정 값 제외하고 복사
std::remove_copy는 특정 값을 제외하고 나머지만 복사합니다. 원본을 수정하지 않고 필터링된 결과를 얻을 때 유용합니다.
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
std::vector<int> src = {1, 2, 3, 2, 4, 2, 5};
std::vector<int> dst;
// 2를 제외하고 복사
std::remove_copy(src.begin(), src.end(), std::back_inserter(dst), 2);
for (int x : dst) {
std::cout << x << " "; // 1 3 4 5
}
std::cout << "\n";
// src는 변경되지 않음
std::cout << "원본: ";
for (int x : src) {
std::cout << x << " "; // 1 2 3 2 4 2 5
}
}
동작 원리: 각 요소를 비교하여 지정된 값과 다른 요소만 목적지에 복사합니다. std::remove와 달리 원본을 수정하지 않습니다.
실무 활용:
- 설정 파일에서 주석 라인 제외
- 데이터에서 NULL 값 제거
- 로그에서 특정 패턴 제외
// 실무 예시: 빈 문자열 제외하고 복사
std::vector<std::string> lines = {"hello", "", "world", "", "C++"};
std::vector<std::string> non_empty;
std::remove_copy(lines.begin(), lines.end(),
std::back_inserter(non_empty), "");
// non_empty: {"hello", "world", "C++"}
remove_copy_if 변형: 조건으로 제거할 요소를 지정할 수도 있습니다.
// 음수 제외하고 복사
std::vector<int> numbers = {-1, 2, -3, 4, -5};
std::vector<int> positives;
std::remove_copy_if(numbers.begin(), numbers.end(),
std::back_inserter(positives),
[](int x) { return x < 0; });
// positives: {2, 4}
복사 알고리즘 전체 목록
STL은 다양한 복사 알고리즘을 제공합니다. 각각의 특징과 사용 시기를 이해하면 적절한 도구를 선택할 수 있습니다.
// 기본 복사
std::copy(begin, end, out) // 전체 범위 복사
std::copy_if(begin, end, out, pred) // 조건 만족 요소만
std::copy_n(begin, n, out) // 처음 N개만
// 역방향 복사 (겹치는 범위에서 안전)
std::copy_backward(begin, end, out) // 끝에서부터 복사
// 이동 (복사 회피)
std::move(begin, end, out) // 이동 생성자 사용
std::move_backward(begin, end, out) // 역방향 이동
// 조건부 복사
std::remove_copy(begin, end, out, value) // 특정 값 제외
std::remove_copy_if(begin, end, out, pred) // 조건 만족 요소 제외
알고리즘 선택 가이드
| 상황 | 알고리즘 | 이유 |
|---|---|---|
| 전체 복사 | copy | 가장 기본적이고 명확함 |
| 필터링 | copy_if | 조건으로 선택 |
| 일부만 | copy_n | 개수 지정 |
| 큰 객체 | move | 복사 비용 절감 |
| 겹치는 범위 | copy_backward | 오른쪽으로 이동 시 안전 |
| 특정 값 제외 | remove_copy | 원본 유지하며 필터링 |
자주 발생하는 문제
문제 1: 목적지 크기 부족
증상: 목적지 컨테이너의 크기가 부족하면 버퍼 오버플로우가 발생합니다.
std::vector<int> src = {1, 2, 3, 4, 5};
std::vector<int> dst(3); // 크기 3
// ❌ 크기 부족 - 정의되지 않은 동작!
// std::copy(src.begin(), src.end(), dst.begin()); // 버퍼 오버플로우
// ✅ 해결 1: back_inserter 사용 (자동 확장)
std::vector<int> dst2;
std::copy(src.begin(), src.end(), std::back_inserter(dst2));
// ✅ 해결 2: 미리 resize
dst.resize(src.size());
std::copy(src.begin(), src.end(), dst.begin());
// ✅ 해결 3: reserve + back_inserter (메모리 재할당 최소화)
std::vector<int> dst3;
dst3.reserve(src.size()); // 메모리 미리 확보
std::copy(src.begin(), src.end(), std::back_inserter(dst3));
왜 이런 일이?: std::copy는 목적지 반복자에 대입만 합니다. dst.begin()은 기존 요소를 가리키므로, 크기가 부족하면 범위를 벗어난 메모리에 쓰게 됩니다.
성능 고려: back_inserter는 편리하지만 push_back()을 반복 호출하므로, 크기를 알면 reserve()로 미리 메모리를 확보하는 것이 재할당을 줄여 더 빠릅니다.
문제 2: 겹치는 범위
증상: 원본과 목적지 범위가 겹치면 정의되지 않은 동작(UB) 이 발생할 수 있습니다.
std::vector<int> v = {1, 2, 3, 4, 5};
// ❌ 겹치는 범위 - 오른쪽으로 복사 시 데이터 손실
// std::copy(v.begin(), v.begin() + 3, v.begin() + 1); // UB!
// 의도: {1, 1, 2, 3, 5}
// 실제: {1, 1, 1, 1, 5} (복사 중 덮어써짐)
// ✅ 해결 1: copy_backward (오른쪽으로 이동 시)
std::vector<int> v2 = {1, 2, 3, 4, 5};
std::copy_backward(v2.begin(), v2.begin() + 3, v2.begin() + 4);
// v2: {1, 1, 2, 3, 5}
// ✅ 해결 2: 왼쪽으로는 copy 사용 가능
std::vector<int> v3 = {1, 2, 3, 4, 5};
std::copy(v3.begin() + 2, v3.end(), v3.begin());
// v3: {3, 4, 5, 4, 5}
// ✅ 해결 3: memmove (POD 타입만, C 스타일)
// memmove(v.data() + 1, v.data(), 3 * sizeof(int));
왜 이런 일이?: std::copy는 왼쪽에서 오른쪽으로 순차 복사합니다. 오른쪽으로 이동할 때 아직 복사하지 않은 원본 데이터를 먼저 덮어쓰면, 이후 복사할 데이터가 손상됩니다.
copy_backward 동작: 끝에서부터 역순으로 복사하므로, 오른쪽으로 이동 시 원본 데이터가 덮어써지기 전에 복사됩니다.
실무 팁:
- 오른쪽 이동:
copy_backward사용 - 왼쪽 이동:
copy사용 가능 - 확실하지 않으면: 임시 컨테이너에 복사 후 다시 대입
문제 3: move 후 원본 상태
증상: std::move 알고리즘 후 원본 요소는 유효하지만 지정되지 않은 상태(valid but unspecified) 가 됩니다.
std::vector<std::string> src = {"hello", "world"};
std::vector<std::string> dst;
std::move(src.begin(), src.end(), std::back_inserter(dst));
// src는 유효하지만 지정되지 않은 상태
// ❌ src[0] 사용 - 빈 문자열일 수도, 아닐 수도 있음 (UB는 아니지만 위험)
std::cout << src[0].size() << '\n'; // 0일 가능성 높지만 보장 안됨
// ✅ 안전한 사용법
src.clear(); // 명시적으로 비우기
src = {"new", "data"}; // 재할당
왜 이런 상태가?: 이동 후 원본 객체는 소멸자를 안전하게 호출할 수 있는 상태여야 하지만, 값은 보장되지 않습니다. std::string의 경우 보통 빈 문자열이 되지만, 표준은 이를 보장하지 않습니다.
실무 권장사항:
- 이동 후 원본 사용 금지: 이동 후에는 원본을 읽지 마세요.
- 재사용 전 초기화:
clear(),reset(), 또는 재할당으로 명확한 상태로 만드세요. - 임시 객체 이동: 어차피 버릴 객체만 이동하세요.
// ✅ 좋은 패턴: 임시 벡터 이동
std::vector<Data> parse_file(const std::string& path) {
std::vector<Data> temp;
// ... 파싱 ...
return temp; // 자동 이동 (RVO/NRVO)
}
auto data = parse_file("input.txt"); // 이동됨, temp는 더 이상 접근 불가
문제 4: 복사 vs 이동 성능
증상: 큰 객체를 copy로 복사하면 성능이 크게 저하됩니다.
std::vector<std::string> src(10000, "very long string with lots of data...");
std::vector<std::string> dst;
dst.reserve(src.size());
// copy: 복사 생성자 10,000번 호출
auto start = std::chrono::steady_clock::now();
std::copy(src.begin(), src.end(), std::back_inserter(dst));
auto copy_time = std::chrono::steady_clock::now() - start;
dst.clear();
// move: 이동 생성자 10,000번 호출 (포인터만 이동)
start = std::chrono::steady_clock::now();
std::move(src.begin(), src.end(), std::back_inserter(dst));
auto move_time = std::chrono::steady_clock::now() - start;
// move_time << copy_time (보통 10~100배 빠름)
성능 비교:
| 타입 | copy | move | 비율 |
|---|---|---|---|
int | 빠름 | 비슷 | ~1x |
std::string (짧은 문자열) | 보통 | 빠름 | ~5x |
std::string (긴 문자열) | 느림 | 빠름 | ~50x |
std::vector<int> | 느림 | 빠름 | ~100x |
| 사용자 정의 클래스 | 구현에 따라 | 빠름 | 다양 |
실무 가이드:
- 작은 타입 (
int,double,char):copy사용 (이동 이점 없음) - 문자열/컨테이너: 원본이 필요 없으면
move사용 - 사용자 정의 타입: 이동 생성자가 있으면
move고려 - const 객체: 이동 불가,
copy만 가능
// 큰 객체는 move 권장
std::vector<HugeObject> src = /* ... */;
std::vector<HugeObject> dst;
dst.reserve(src.size());
if (/* 원본이 더 이상 필요 없으면 */) {
std::move(src.begin(), src.end(), std::back_inserter(dst));
} else {
std::copy(src.begin(), src.end(), std::back_inserter(dst));
}
출력 반복자 (Output Iterator)
복사 알고리즘의 목적지는 출력 반복자입니다. STL은 다양한 출력 반복자를 제공하여 복사 동작을 유연하게 만듭니다.
#include <algorithm>
#include <vector>
#include <iterator>
#include <iostream>
std::vector<int> src = {1, 2, 3};
// 1. back_inserter: push_back 호출
std::vector<int> dst1;
std::copy(src.begin(), src.end(), std::back_inserter(dst1));
// dst1: {1, 2, 3}
// 2. inserter: insert 호출 (지정된 위치에 삽입)
std::vector<int> dst2 = {10, 20};
std::copy(src.begin(), src.end(), std::inserter(dst2, dst2.begin()));
// dst2: {1, 2, 3, 10, 20}
// 3. front_inserter: push_front 호출 (list, deque만)
std::list<int> dst3;
std::copy(src.begin(), src.end(), std::front_inserter(dst3));
// dst3: {3, 2, 1} (역순)
// 4. ostream_iterator: 스트림에 출력
std::copy(src.begin(), src.end(),
std::ostream_iterator<int>(std::cout, " "));
// 출력: 1 2 3
출력 반복자 선택 가이드
| 반복자 | 호출 메서드 | 지원 컨테이너 | 사용 시기 |
|---|---|---|---|
back_inserter | push_back() | vector, deque, list, string | 끝에 추가 (가장 흔함) |
front_inserter | push_front() | list, deque | 앞에 추가 (역순) |
inserter | insert() | 모든 컨테이너 | 중간 위치에 삽입 |
ostream_iterator | operator<< | 스트림 | 파일/콘솔 출력 |
성능 고려:
back_inserter: vector는 재할당 가능,reserve()권장front_inserter: list는 O(1), vector는 지원 안 함inserter: 중간 삽입은 vector에서 O(N), list에서 O(1)
실무 예시:
// 파일로 복사
std::ofstream file("output.txt");
std::copy(data.begin(), data.end(),
std::ostream_iterator<int>(file, "\n"));
// set에 복사 (중복 자동 제거)
std::vector<int> v = {1, 2, 2, 3, 3, 3};
std::set<int> s;
std::copy(v.begin(), v.end(), std::inserter(s, s.end()));
// s: {1, 2, 3}
성능 최적화 팁
1. reserve로 재할당 최소화
std::vector<int> src(10000);
std::vector<int> dst;
// ❌ 느림: 여러 번 재할당
std::copy(src.begin(), src.end(), std::back_inserter(dst));
// ✅ 빠름: 한 번에 메모리 확보
dst.reserve(src.size());
std::copy(src.begin(), src.end(), std::back_inserter(dst));
2. 병렬 복사 (C++17)
#include <execution>
// 순차 복사
std::copy(src.begin(), src.end(), dst.begin());
// 병렬 복사 (멀티코어 활용)
std::copy(std::execution::par, src.begin(), src.end(), dst.begin());
3. memcpy로 최적화 (POD 타입만)
// POD 타입은 memcpy가 더 빠를 수 있음
std::vector<int> src(1000000);
std::vector<int> dst(src.size());
// std::copy 대신
std::memcpy(dst.data(), src.data(), src.size() * sizeof(int));
주의: memcpy는 POD 타입(Plain Old Data)에만 안전합니다. std::string, std::vector 같은 타입은 절대 사용하지 마세요.
FAQ
Q1: copy는 언제 사용하나요?
A: 전체 범위를 복사할 때 사용합니다. 가장 기본적이고 명확한 복사 방법입니다.
Q2: copy_if는 언제 필요한가요?
A: 조건을 만족하는 요소만 선택적으로 복사할 때 사용합니다. 필터링이 필요한 모든 상황에 적합합니다.
Q3: move는 언제 사용해야 하나요?
A: 큰 객체(문자열, 컨테이너)를 복사하고 원본이 더 이상 필요 없을 때 사용합니다. 복사 비용을 크게 줄일 수 있습니다.
Q4: remove_copy와 remove의 차이는?
A:
- remove_copy: 원본 유지, 결과를 새 컨테이너에 복사
- remove: 원본 수정, erase-remove idiom과 함께 사용
Q5: 겹치는 범위는 어떻게 처리하나요?
A: 오른쪽으로 이동 시 copy_backward, 왼쪽으로는 copy 사용. 확실하지 않으면 임시 컨테이너를 거치세요.
Q6: 복사 알고리즘 학습 리소스는?
A:
- “Effective STL” by Scott Meyers
- “C++ Primer” by Lippman
- cppreference.com - std::copy
관련 글: STL 알고리즘 가이드, 반복자 가이드, 범위 기반 for.
한 줄 요약: STL 복사 알고리즘으로 범위를 안전하고 효율적으로 복사하고, 큰 객체는 move로 성능을 높이세요.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ Algorithm Replace | “치환 알고리즘” 가이드
- C++ Algorithm Reverse | “역순 알고리즘” 가이드
- C++ Algorithm Generate | “생성 알고리즘” 가이드
실전 팁
실무에서 바로 적용할 수 있는 팁입니다.
디버깅 팁
- 문제가 발생하면 먼저 컴파일러 경고를 확인하세요
- 간단한 테스트 케이스로 문제를 재현하세요
성능 팁
- 프로파일링 없이 최적화하지 마세요
- 측정 가능한 지표를 먼저 설정하세요
코드 리뷰 팁
- 코드 리뷰에서 자주 지적받는 부분을 미리 체크하세요
- 팀의 코딩 컨벤션을 따르세요
이 글에서 다루는 키워드 (관련 검색어)
C++, algorithm, copy, move, STL 등으로 검색하시면 이 글이 도움이 됩니다.
관련 글
- C++ Algorithm Count |
- C++ Algorithm Generate |
- C++ 알고리즘 |
- C++ Algorithm Heap |
- C++ Algorithm MinMax |