C++ std::copy·copy_if·copy_backward: 목적지 크기, 겹치는 범위, back_inserter

이 글의 핵심

std::copy는 목적지 공간을 확보해 주지 않기 때문에 빈 벡터에 그대로 복사하면 정의되지 않은 동작이 됩니다. 겹치는 범위에서 copy_backward가 필요한 이유, std::copy와 memcpy의 관계, reserve로 재할당을 줄이는 방법과 C++17 병렬 복사까지 다뤄 상황에 맞는 복사 알고리즘을 고를 수 있게 합니다.

복사 알고리즘이란?

복사 알고리즘은 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_nfirst부터 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번 할당)
std::copy(src.begin(), src.end(), std::back_inserter(dst));

// 이동: string 객체(보통 24~32바이트)만 옮기고 버퍼 소유권을 넘김 (할당 없음)
std::move(src.begin(), src.end(), std::back_inserter(dst));

차이의 대부분은 바이트 수가 아니라 힙 할당 횟수에서 나옵니다. 문자열이 길수록, 개수가 많을수록 복사 쪽의 할당·memcpy 비용이 커져 격차가 벌어집니다. 반대로 SSO(Small String Optimization) 범위에 들어가는 짧은 문자열은 원래 힙 할당이 없어서 복사와 이동의 차이가 거의 없습니다.

실무 활용:

  • 임시 벡터를 다른 컨테이너로 이동
  • 파싱 결과를 캐시로 이동
  • 큐에서 작업 항목을 워커로 이동

예시 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의 경우 보통 빈 문자열이 되지만, 표준은 이를 보장하지 않습니다.

실무 권장사항:

  1. 이동 후 원본 사용 금지: 이동 후에는 원본을 읽지 마세요.
  2. 재사용 전 초기화: clear(), reset(), 또는 재할당으로 명확한 상태로 만드세요.
  3. 임시 객체 이동: 어차피 버릴 객체만 이동하세요.
// ✅ 좋은 패턴: 임시 벡터 이동
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보다 훨씬 작게 나옵니다 (배율은 문자열 길이·할당자에 따라 다름)

성능 비교:

타입copy 비용move 비용차이가 나는 이유
int값 복사값 복사이동도 복사와 같음
std::string (SSO 범위의 짧은 문자열)내부 버퍼 복사내부 버퍼 복사힙 할당이 없어 차이 거의 없음
std::string (긴 문자열)힙 할당 + 내용 복사포인터·크기만 이동길이에 비례해 격차 증가
std::vector<int>힙 할당 + 원소 전체 복사포인터 3개 이동원소 수에 비례해 격차 증가
사용자 정의 클래스구현에 따라이동 생성자 구현에 따라noexcept 이동 생성자가 없으면 복사로 대체되기도 함

실무 가이드:

  • 작은 타입 (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_inserterpush_back()vector, deque, list, string끝에 추가 (가장 흔함)
front_inserterpush_front()list, deque앞에 추가 (역순)
inserterinsert()모든 컨테이너중간 위치에 삽입
ostream_iteratoroperator<<스트림파일/콘솔 출력

성능 고려:

  • 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}

성능 최적화 팁

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));

병렬 복사 (C++17)

#include <execution>

// 순차 복사
std::copy(src.begin(), src.end(), dst.begin());

// 병렬 복사 (멀티코어 활용)
std::copy(std::execution::par, src.begin(), src.end(), dst.begin());

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:

관련 글: STL 알고리즘 가이드, 반복자 가이드, 범위 기반 for.

STL 복사 알고리즘으로 범위를 안전하고 효율적으로 복사하며, 큰 객체는 move로 성능을 높이세요.


같이 보면 좋은 글

실전 팁 (C++)

  • 컴파일러 경고를 최대로 켜고(-Wall -Wextra 등 팀 합의), Sanitizer(ASan/UBSan)로 미정의 동작을 조기에 잡습니다.
  • 최적화는 프로파일 결과를 본 뒤에 합니다.
  • STL <algorithm> 사용 시 반복자 무효화·비교자 일관성을 함께 검토합니다.