[2026] C++ STL 알고리즘 기초 완벽 가이드 | sort·find

[2026] C++ STL 알고리즘 기초 완벽 가이드 | sort·find

이 글의 핵심

C++ STL 알고리즘 기초 입니다. sort, find, transform, accumulate 등 핵심 알고리즘의 사용법과 성능 특성을 실전 예제로 설명합니다. 데이터 처리 코드를 작성할 때마다 직접 for문을 돌리다 보면 이런 일이 반복됩니다: 인덱스 범위를 잘못 써서 Segmentation fault가 나요. 정렬할 때 같은 값 처리 로직을 빼먹었어요.

들어가며: “for문 작성하다 보니 버그가 자꾸 생겨요”

실제 겪는 문제 시나리오

데이터 처리 코드를 작성할 때마다 직접 for문을 돌리다 보면 이런 일이 반복됩니다: 아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

"인덱스 범위를 잘못 써서 Segmentation fault가 나요."
"정렬할 때 같은 값 처리 로직을 빼먹었어요."
"값을 찾을 때 존재하지 않으면 end() 체크를 깜빡했어요."
"조건에 맞는 원소를 제거하려다 반복자 무효화로 크래시가 났어요."
"합계·평균 계산할 때 초기값 타입을 잘못 넣어서 부동소수점 오차가 나요."

STL 알고리즘으로 해결:

문제STL 해결
인덱스 오류[begin, end) 반복자로 범위 명시, 경계 검증된 구현 사용
정렬 실수std::sort, std::stable_sort — 검증된 O(n log n) 구현
검색 실수std::find, std::find_if — end 체크 패턴 통일
개수 세기std::count, std::count_if — 한 줄로 집계
변환 실수std::transform — 선언적 변환, 출력 범위만 확보
복사 실수std::copy, std::copy_if — 범위 기반 복사
제거 실수std::remove + erase — erase-remove idiom
요구 환경: C++17 이상 권장
이 글을 읽으면:
  • sort, find, count, transform, accumulate, copy, remove를 완전히 이해할 수 있습니다.
  • 문제 상황별 적절한 알고리즘을 선택할 수 있습니다.
  • 흔한 에러를 피하고 베스트 프랙티스를 적용할 수 있습니다.
  • 프로덕션 수준의 패턴을 사용할 수 있습니다.

실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 문제 시나리오 상세
  2. 정렬: sort
  3. 검색: find
  4. 개수: count
  5. 변환: transform
  6. 집계: accumulate
  7. 복사: copy
  8. 제거: remove
  9. 완전한 예제 모음
  10. 자주 발생하는 에러와 해결법
  11. 베스트 프랙티스
  12. 프로덕션 패턴
  13. 구현 체크리스트

1. 문제 시나리오 상세

시나리오 1: 대량 데이터 정렬 시 성능·정확성 문제

상황: 수십만 건의 로그를 타임스탬프 기준으로 정렬해야 합니다. 직접 버블 정렬을 구현하면 O(n²)로 시간 초과가 발생하고, 같은 값의 순서를 잘못 처리하기 쉽습니다. 해결: std::sort는 인트로소트(퀵소트 + 힙소트 + 삽입정렬)로 O(n log n)에 정렬합니다. 같은 값의 순서가 중요하면 std::stable_sort를 사용합니다.

시나리오 2: 컨테이너에서 값 검색 시 end 체크 누락

상황: ID 목록에서 특정 ID가 있는지 확인하고, 있으면 인덱스를 사용합니다. for문으로 돌리다 vec[i] == target 체크 후 i를 사용하는데, 없을 때 -1이나 size()를 반환하는 로직을 빼먹기 쉽습니다. 해결: std::find는 “찾으면 반복자, 없으면 end”를 반환합니다. it != vec.end()로 존재 여부를 일관되게 확인할 수 있습니다.

시나리오 3: 조건 만족 개수 세기

상황: “에러 로그가 몇 개인지”, “80점 이상이 몇 명인지” 등을 세어야 합니다. 수동 카운터를 쓰면 초기화를 빼먹거나, 다른 변수와 섞이기 쉽습니다. 해결: std::count, std::count_if로 한 줄에 집계합니다.

시나리오 4: 데이터 변환 파이프라인

상황: JSON 파싱 결과를 DTO로 변환하거나, 센서 값을 정규화하는 등 “각 원소에 함수 적용”이 반복됩니다. for문은 가독성이 떨어지고 인덱스 실수가 납니다. 해결: std::transform으로 변환 로직을 선언적으로 표현합니다.

시나리오 5: 합계·평균·곱셈 등 집계

상황: 벡터의 합, 곱, 또는 문자열 리스트를 하나로 연결하는 “접기(fold)” 연산이 필요합니다. 수동 루프는 초기값 처리나 타입 변환에서 실수하기 쉽습니다. 해결: std::accumulate로 초기값부터 순차적으로 이항 함수를 적용해 집계합니다.

시나리오 6: 조건에 맞는 원소만 복사

상황: “양수만”, “에러 레벨만” 등 조건을 만족하는 원소만 새 컨테이너로 복사해야 합니다. for문으로 push_back하다 범위 오류가 나기 쉽습니다. 해결: std::copy_if + std::back_inserter로 한 줄에 처리합니다.

시나리오 7: 조건에 맞는 원소 제거

상황: “짝수 제거”, “만료된 항목 제거” 등입니다. 반복문 안에서 erase를 호출하면 반복자 무효화로 크래시가 납니다. 해결: std::remove_if + vec.erase 조합(erase-remove idiom)으로 안전하게 제거합니다.

STL 알고리즘 분류 다이어그램

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
    subgraph sort[정렬]
        S1[sort]
        S2[stable_sort]
        S3[partial_sort]
    end
    subgraph search[검색]
        F1[find]
        F2[find_if]
        F3[find_if_not]
    end
    subgraph count[개수]
        C1[count]
        C2[count_if]
    end
    subgraph transform[변환]
        T1[transform]
    end
    subgraph agg[집계]
        A1[accumulate]
    end
    subgraph copy[복사]
        CP1[copy]
        CP2[copy_if]
    end
    subgraph remove[제거]
        R1[remove]
        R2[remove_if]
    end
    input["[begin, end) 반복자"] --> sort
    input --> search
    input --> count
    input --> transform
    input --> agg
    input --> copy
    input --> remove

2. 정렬: sort

std::sort: 기본 정렬

std::sort반개구간 [begin, end) 에 있는 원소를 기본적으로 오름차순으로 정렬합니다. 내부적으로 인트로소트를 사용하며, 비교 연산만 정의되어 있으면 어떤 타입이든 정렬할 수 있습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// g++ -std=c++17 -o sort_basic sort_basic.cpp && ./sort_basic
#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    std::sort(vec.begin(), vec.end());
    for (int x : vec) std::cout << x << " ";  // 1 2 3 5 7 8 9
    std::cout << "\n";
    // 내림차순
    std::sort(vec.begin(), vec.end(), std::greater<int>());
    for (int x : vec) std::cout << x << " ";  // 9 8 7 5 3 2 1
    std::cout << "\n";
    return 0;
}

주의: std::sort같은 값끼리의 원래 순서를 보장하지 않습니다. 같은 값의 순서가 중요하면 std::stable_sort를 사용하세요.

커스텀 비교자

구조체나 클래스를 정렬할 때는 비교 함수로 기준을 넘깁니다. 람다 { return a.x < b.x; }는 “a가 b보다 앞에 오려면 a.x < b.x”를 의미합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <string>
struct LogEntry {
    std::string timestamp;
    int level;
    std::string message;
};
int main() {
    std::vector<LogEntry> logs = {
        {"2026-04-23 10:00:00", 2, "Warning"},
        {"2026-04-23 09:00:00", 1, "Info"},
        {"2026-04-23 10:00:00", 1, "Error"}
    };
    // 타임스탬프 오름차순, 같으면 level 오름차순
    std::sort(logs.begin(), logs.end(),
         {
            if (a.timestamp != b.timestamp)
                return a.timestamp < b.timestamp;
            return a.level < b.level;
        });
    return 0;
}

stable_sort: 안정 정렬

같은 값끼리 원래 입력 순서를 유지해야 할 때 사용합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
struct Student {
    std::string name;
    int score;
};
int main() {
    std::vector<Student> students = {
        {"Alice", 90}, {"Bob", 85}, {"Charlie", 90}, {"David", 85}
    };
    std::stable_sort(students.begin(), students.end(),
         { return a.score > b.score; });
    // Alice(90), Charlie(90), Bob(85), David(85) - 같은 점수 내 순서 유지
    return 0;
}

partial_sort: 상위 k개만 정렬

전체를 정렬할 필요 없이 “상위 N개만 올바른 순서”가 필요할 때 사용합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    // 상위 3개만 정렬 (가장 큰 3개)
    std::partial_sort(vec.begin(), vec.begin() + 3, vec.end(),
                      std::greater<int>());
    for (int i = 0; i < 3; ++i)
        std::cout << vec[i] << " ";  // 9 8 7
    std::cout << "\n";
    return 0;
}

3. 검색: find

std::find: 값으로 검색

std::find선형 검색으로 범위 내에서 value와 같은 첫 번째 원소의 반복자를 반환합니다. 없으면 end를 반환합니다. O(n)이지만 정렬이 필요 없습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    auto it = std::find(vec.begin(), vec.end(), 8);
    if (it != vec.end()) {
        std::cout << "Found at index: " << std::distance(vec.begin(), it) << "\n";  // 2
    } else {
        std::cout << "Not found\n";
    }
    // 없는 값
    auto it2 = std::find(vec.begin(), vec.end(), 100);
    if (it2 == vec.end()) {
        std::cout << "100 not found\n";
    }
    return 0;
}

std::find_if: 조건으로 검색

조건(술어)을 만족하는 첫 번째 원소의 반복자를 반환합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    // 5보다 큰 첫 번째 원소
    auto it = std::find_if(vec.begin(), vec.end(),
         { return x > 5; });
    if (it != vec.end()) {
        std::cout << "First > 5: " << *it << "\n";  // 8
    }
    // 짝수인 첫 번째 원소
    auto it2 = std::find_if(vec.begin(), vec.end(),
         { return x % 2 == 0; });
    if (it2 != vec.end()) {
        std::cout << "First even: " << *it2 << "\n";  // 2
    }
    return 0;
}

std::find_if_not: 조건을 만족하지 않는 첫 원소

조건을 만족하지 않는 첫 번째 원소의 반복자를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
int main() {
    std::vector<int> vec = {2, 4, 6, 7, 8, 10};
    // 홀수인 첫 번째 원소
    auto it = std::find_if_not(vec.begin(), vec.end(),
         { return x % 2 == 0; });
    if (it != vec.end()) {
        // *it == 7
    }
    return 0;
}

4. 개수: count

std::count: 값과 같은 원소 개수

범위 내에서 value와 같은 원소의 개수를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 2, 2, 3, 4, 2, 5};
    size_t n = std::count(vec.begin(), vec.end(), 2);
    std::cout << "Count of 2: " << n << "\n";  // 4
    n = std::count(vec.begin(), vec.end(), 10);
    std::cout << "Count of 10: " << n << "\n";  // 0
    return 0;
}

std::count_if: 조건 만족 개수

조건(술어)을 만족하는 원소의 개수를 반환합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {5, 2, 8, 1, 9, 3, 7};
    // 5보다 큰 원소 개수
    size_t n = std::count_if(vec.begin(), vec.end(),
         { return x > 5; });
    std::cout << "Count > 5: " << n << "\n";  // 3
    // 짝수 개수
    n = std::count_if(vec.begin(), vec.end(),
         { return x % 2 == 0; });
    std::cout << "Even count: " << n << "\n";  // 2
    return 0;
}

5. 변환: transform

std::transform: 단항 변환

범위의 각 원소에 함수(람다)를 적용한 결과를 출력 범위에 씁니다. 출력을 입력과 같은 범위로 주면 제자리(in-place) 변환이 됩니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <cmath>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::vector<int> result(vec.size());
    // 각 원소 2배
    std::transform(vec.begin(), vec.end(), result.begin(),
                   { return x * 2; });
    // result: {2, 4, 6, 8, 10}
    // 제자리 변환 (원본 수정)
    std::transform(vec.begin(), vec.end(), vec.begin(),
                   { return x * 2; });
    // vec: {2, 4, 6, 8, 10}
    // 실수 변환 (sqrt)
    std::vector<double> values = {1.0, 4.0, 9.0, 16.0};
    std::vector<double> roots(values.size());
    std::transform(values.begin(), values.end(), roots.begin(),
                   { return std::sqrt(x); });
    // roots: {1, 2, 3, 4}
    return 0;
}

std::transform: 두 범위 결합 (이항 변환)

두 시퀀스의 같은 위치 원소를 한 번에 받아 결과를 출력합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> a = {1, 2, 3, 4, 5};
    std::vector<int> b = {10, 20, 30, 40, 50};
    std::vector<int> sum(a.size());
    std::transform(a.begin(), a.end(), b.begin(), sum.begin(),
                   { return x + y; });
    // sum: {11, 22, 33, 44, 55}
    return 0;
}

6. 집계: accumulate

std::accumulate: 기본 동작

std::accumulate왼쪽부터 차례로 접어 나가는(fold) 연산입니다. 세 번째 인자는 초기값이고, 기본 동작은 초기값 + vec[0] + vec[1] + ...입니다. <numeric> 헤더가 필요합니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    I["초기값 0"] --> A1["+ vec[0]"]
    A1 --> A2["+ vec[1]"]
    A2 --> A3["+ vec[2]"]
    A3 --> R["최종 결과"]

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <numeric>
#include <vector>
#include <string>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5};
    int sum = std::accumulate(vec.begin(), vec.end(), 0);
    std::cout << "Sum: " << sum << "\n";  // 15
    int product = std::accumulate(vec.begin(), vec.end(), 1,
         { return a * b; });
    std::cout << "Product: " << product << "\n";  // 120
    std::vector<std::string> words = {"Hello", " ", "World"};
    std::string concat = std::accumulate(words.begin(), words.end(), std::string(),
         { return a + b; });
    std::cout << concat << "\n";  // "Hello World"
    return 0;
}

accumulate로 평균, 분산 계산

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <numeric>
#include <vector>
#include <cmath>
double mean(const std::vector<double>& vec) {
    if (vec.empty()) return 0;
    double sum = std::accumulate(vec.begin(), vec.end(), 0.0);
    return sum / vec.size();
}
double variance(const std::vector<double>& vec) {
    if (vec.size() < 2) return 0;
    double m = mean(vec);
    double sq_diff = std::accumulate(vec.begin(), vec.end(), 0.0,
        [m](double a, double x) { return a + (x - m) * (x - m); });
    return sq_diff / (vec.size() - 1);
}

7. 복사: copy

std::copy: 전체 복사

[first, last) 범위의 원소를 출력 반복자로 복사합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst(src.size());
    std::copy(src.begin(), src.end(), dst.begin());
    // dst: {1, 2, 3, 4, 5}
    // back_inserter로 동적 추가
    std::vector<int> dst2;
    dst2.reserve(src.size());
    std::copy(src.begin(), src.end(), std::back_inserter(dst2));
    return 0;
}

std::copy_if: 조건 만족 원소만 복사

조건(술어)을 만족하는 원소만 출력 범위로 복사합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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> even;
    std::copy_if(src.begin(), src.end(), std::back_inserter(even),
         { return x % 2 == 0; });
    // even: {2, 4, 6, 8, 10}
    // reserve로 재할당 최소화
    std::vector<int> result;
    result.reserve(src.size());
    std::copy_if(src.begin(), src.end(), std::back_inserter(result),
         { return x > 5; });
    // result: {6, 7, 8, 9, 10}
    return 0;
}

std::copy_n: n개만 복사

처음 n개 원소만 복사합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
int main() {
    std::vector<int> src = {1, 2, 3, 4, 5};
    std::vector<int> dst(3);
    std::copy_n(src.begin(), 3, dst.begin());
    // dst: {1, 2, 3}
    return 0;
}

8. 제거: remove

remove의 동작 방식 (주의)

std::removestd::remove_if실제로 원소를 삭제하지 않습니다. 제거할 값들을 뒤로 밀어내고, “새 논리적 끝” 반복자를 반환합니다. 물리적 크기는 그대로이므로, 반드시 erase와 함께 사용해야 합니다. 아래 코드는 mermaid를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

flowchart LR
    subgraph before[remove 전]
        B1[1] --> B2[2] --> B3[2] --> B4[3] --> B5[2]
    end
    subgraph after["remove 후 (erase 전)"]
        A1[1] --> A2[3] --> A3[?] --> A4[?] --> A5[?]
    end

erase-remove idiom

조건에 맞는 원소를 실제로 제거하려면 remove_if의 반환값을 erase에 넘깁니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> vec = {1, 2, 3, 2, 4, 2, 5};
    // 값 2 제거
    vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());
    // vec: {1, 3, 4, 5}
    // 짝수 제거
    std::vector<int> vec2 = {1, 2, 3, 4, 5, 6};
    vec2.erase(std::remove_if(vec2.begin(), vec2.end(),
         { return x % 2 == 0; }), vec2.end());
    // vec2: {1, 3, 5}
    return 0;
}

std::remove: 값으로 제거

지정한 값과 같은 모든 원소를 “제거”하고, 새 끝 반복자를 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 2, 3, 2, 4};
    auto new_end = std::remove(vec.begin(), vec.end(), 2);
    vec.erase(new_end, vec.end());
    // vec: {1, 3, 4}
    return 0;
}

std::remove_if: 조건으로 제거

조건을 만족하는 모든 원소를 제거합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <vector>
int main() {
    std::vector<int> vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    // 5보다 큰 원소 제거
    vec.erase(std::remove_if(vec.begin(), vec.end(),
         { return x > 5; }), vec.end());
    // vec: {1, 2, 3, 4, 5}
    return 0;
}

9. 완전한 예제 모음

예제 1: 로그 분석기 (sort + find + count + copy + accumulate)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// g++ -std=c++17 -o log_analyzer log_analyzer.cpp && ./log_analyzer
#include <algorithm>
#include <numeric>
#include <vector>
#include <string>
#include <iostream>
struct LogEntry {
    std::string timestamp;
    int level;  // 0=DEBUG, 1=INFO, 2=WARN, 3=ERROR
    std::string message;
};
int main() {
    std::vector<LogEntry> logs = {
        {"2026-04-23 10:00:00", 2, "Connection timeout"},
        {"2026-04-23 09:55:00", 1, "User login"},
        {"2026-04-23 10:01:00", 3, "Disk full"},
        {"2026-04-23 09:58:00", 1, "Request received"},
        {"2026-04-23 10:00:30", 2, "Retry attempt"}
    };
    // 1. sort: 타임스탬프 오름차순 정렬
    std::sort(logs.begin(), logs.end(),
         {
            return a.timestamp < b.timestamp;
        });
    // 2. count_if: ERROR(3) 개수
    int error_count = std::count_if(logs.begin(), logs.end(),
         { return e.level == 3; });
    std::cout << "Errors: " << error_count << "\n";
    // 3. copy_if: WARN 이상만 필터링
    std::vector<LogEntry> critical;
    std::copy_if(logs.begin(), logs.end(), std::back_inserter(critical),
         { return e.level >= 2; });
    // 4. find_if: 첫 ERROR 찾기
    auto it = std::find_if(logs.begin(), logs.end(),
         { return e.level == 3; });
    if (it != logs.end()) {
        std::cout << "First error: " << it->message << "\n";
    }
    // 5. accumulate: level 합계 (평균 레벨 계산용)
    int level_sum = std::accumulate(logs.begin(), logs.end(), 0,
         { return a + e.level; });
    double avg_level = static_cast<double>(level_sum) / logs.size();
    std::cout << "Avg level: " << avg_level << "\n";
    return 0;
}

실행 결과:

Errors: 1
First error: Disk full
Avg level: 1.8

예제 2: 점수 처리 파이프라인 (transform + sort + find + remove)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> scores = {72, 85, 91, 68, 78, 88, 95, 62, -1, 0};
    // 1. remove_if: 유효하지 않은 점수(-1, 0) 제거
    scores.erase(std::remove_if(scores.begin(), scores.end(),
         { return s <= 0; }), scores.end());
    // 2. transform: 정규화 (0-100 -> 0.0-1.0)
    std::vector<double> normalized(scores.size());
    std::transform(scores.begin(), scores.end(), normalized.begin(),
         { return s / 100.0; });
    // 3. sort: 상위 5명 (partial_sort)
    std::partial_sort(scores.begin(), scores.begin() + 5, scores.end(),
                     std::greater<int>());
    // 4. find: 80점인 사람이 있는지
    auto it = std::find(scores.begin(), scores.end(), 80);
    bool has_80 = (it != scores.end());
    // 5. accumulate: 평균
    double avg = std::accumulate(scores.begin(), scores.end(), 0.0) / scores.size();
    std::cout << "Average: " << avg << "\n";
    return 0;
}

예제 3: 두 벡터 결합 및 집계 (transform + accumulate)

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#include <algorithm>
#include <numeric>
#include <vector>
#include <iostream>
int main() {
    std::vector<int> prices = {100, 200, 150, 300};
    std::vector<int> quantities = {2, 1, 3, 2};
    // transform: 각 품목별 금액 = price * quantity
    std::vector<int> amounts(prices.size());
    std::transform(prices.begin(), prices.end(), quantities.begin(),
                  amounts.begin(),  { return p * q; });
    // amounts: {200, 200, 450, 600}
    // accumulate: 총 금액
    int total = std::accumulate(amounts.begin(), amounts.end(), 0);
    std::cout << "Total: " << total << "\n";  // 1450
    return 0;
}

10. 자주 발생하는 에러와 해결법

에러 1: find 결과를 end 체크 없이 사용

증상: 찾지 못했을 때 *it 접근으로 Segmentation fault 또는 undefined behavior. 원인: std::findend를 반환했는데, 존재한다고 가정하고 역참조함. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 사용
auto it = std::find(vec.begin(), vec.end(), 100);
int value = *it;  // 100이 없으면 UB!
// ✅ 올바른 사용
auto it = std::find(vec.begin(), vec.end(), 100);
if (it != vec.end()) {
    int value = *it;
} else {
    // 없을 때 처리
}

에러 2: transform 출력 범위 크기 부족

증상: Segmentation fault 또는 메모리 손상. 원인: 출력 범위가 입력보다 작으면 범위를 벗어나 씁니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 잘못된 사용
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> result(2);  // 크기 부족!
std::transform(vec.begin(), vec.end(), result.begin(),  { return x * 2; });
// ✅ 올바른 사용
std::vector<int> result(vec.size());
std::transform(vec.begin(), vec.end(), result.begin(),  { return x * 2; });
// ✅ 또는 back_inserter + reserve
std::vector<int> result;
result.reserve(vec.size());
std::transform(vec.begin(), vec.end(), std::back_inserter(result),
               { return x * 2; });

에러 3: accumulate 초기값 타입 불일치

증상: 정수 합계는 괜찮은데, 부동소수점 합계가 부정확해짐. 원인: std::accumulate(vec.begin(), vec.end(), 0)에서 0은 int이므로, double 벡터를 합할 때 매 단계 int로 변환됩니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 잘못된 사용 (double 벡터에 int 초기값)
std::vector<double> vec = {0.1, 0.2, 0.3};
double sum = std::accumulate(vec.begin(), vec.end(), 0);  // int 0 -> 부정확
// ✅ 올바른 사용
double sum = std::accumulate(vec.begin(), vec.end(), 0.0);  // 0.0

에러 4: remove만 하고 erase 안 함

증상: 벡터 크기는 그대로인데, 뒤쪽에 “쓰레기” 값이 남아 있음. 원인: remove/remove_if는 원소를 삭제하지 않고, 새 논리적 끝만 반환합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 잘못된 사용
std::vector<int> vec = {1, 2, 2, 3};
std::remove(vec.begin(), vec.end(), 2);  // vec 크기 그대로, 뒤에 쓰레기
// ✅ 올바른 사용 (erase-remove idiom)
vec.erase(std::remove(vec.begin(), vec.end(), 2), vec.end());

에러 5: 비교자에서 strict weak ordering 위반

증상: std::sort 호출 시 크래시 또는 무한 루프. 원인: 비교 함수가 a < a가 false, a <= b처럼 <=를 쓰면 a == b일 때 true가 되어 위반. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 잘못된 비교자
std::sort(vec.begin(), vec.end(),
     { return a <= b; });  // a <= a -> true (위반)
// ✅ 올바른 비교자 (< 만 사용)
std::sort(vec.begin(), vec.end(),
     { return a < b; });

에러 6: copy_if에서 출력 범위 reserve 누락

증상: 대량 데이터에서 copy_if + back_inserter 사용 시 재할당이 반복되어 느려짐. 해결: 결과 크기를 대략 알 수 있으면 reserve로 재할당을 줄입니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ reserve로 재할당 최소화
std::vector<int> result;
result.reserve(src.size());  // 최대 src.size()개
std::copy_if(src.begin(), src.end(), std::back_inserter(result),
              { return x % 2 == 0; });

에러 7: empty 범위에서 accumulate 후 나눗셈

증상: vec.empty()일 때 accumulate 결과가 초기값인데, vec.size()로 나누면 0으로 나누기. 다음은 간단한 cpp 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ empty 체크
double avg = vec.empty()
    ? 0.0
    : std::accumulate(vec.begin(), vec.end(), 0.0) / vec.size();

11. 베스트 프랙티스

1. for문 대신 알고리즘 우선

반복문으로 “찾기, 세기, 복사, 제거”를 직접 구현하기 전에, STL에 해당 알고리즘이 있는지 확인합니다. 가독성과 안정성이 좋아집니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ 수동 루프
int count = 0;
for (size_t i = 0; i < vec.size(); ++i) {
    if (vec[i] > 5) ++count;
}
// ✅ count_if
int count = std::count_if(vec.begin(), vec.end(),  { return x > 5; });

2. 람다 비교자 사용 (std::function 피하기)

std::function을 넘기면 간접 호출 비용이 생깁니다. 람다는 컴파일러가 인라인하기 쉽습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ✅ 람다 (인라인 가능)
std::sort(vec.begin(), vec.end(),  { return a < b; });
// ❌ std::function (간접 호출)
std::function<bool(int,int)> cmp =  { return a < b; };
std::sort(vec.begin(), vec.end(), cmp);

3. reserve로 재할당 최소화

back_inserter와 함께 쓸 때, 결과 크기를 대략 알 수 있으면 reserve를 호출합니다. 다음은 간단한 cpp 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::vector<int> result;
result.reserve(vec.size());
std::copy_if(vec.begin(), vec.end(), std::back_inserter(result),
              { return x > 0; });

4. 정렬이 필요할 때만 정렬

한 번만 검색할 거면 std::find(O(n))가 나을 수 있습니다. 여러 번 검색할 때만 정렬 후 binary_search를 고려합니다.

5. partial_sort로 상위 k개만

전체 정렬이 필요 없으면 partial_sortsort보다 빠릅니다.

// 상위 10개만 필요
std::partial_sort(vec.begin(), vec.begin() + 10, vec.end(), std::greater<int>());

12. 프로덕션 패턴

패턴 1: erase-remove idiom

조건에 맞는 원소를 제거할 때 remove_if + erase 조합을 사용합니다. 다음은 간단한 cpp 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 짝수 제거
vec.erase(std::remove_if(vec.begin(), vec.end(),
                          { return x % 2 == 0; }),
          vec.end());

패턴 2: 정렬 + unique로 중복 제거

std::sort(vec.begin(), vec.end());
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());

패턴 3: 파이프라인 체이닝

여러 알고리즘을 순차 적용해 데이터 파이프라인을 구성합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. copy_if: 필터링
std::vector<int> filtered;
std::copy_if(vec.begin(), vec.end(), std::back_inserter(filtered),
              { return x > 0; });
// 2. transform: 변환
std::vector<double> transformed(filtered.size());
std::transform(filtered.begin(), filtered.end(), transformed.begin(),
                { return std::sqrt(static_cast<double>(x)); });
// 3. accumulate: 집계
double sum = std::accumulate(transformed.begin(), transformed.end(), 0.0);

패턴 4: find + distance로 인덱스 얻기

아래 코드는 cpp를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

auto it = std::find(vec.begin(), vec.end(), target);
if (it != vec.end()) {
    size_t index = std::distance(vec.begin(), it);
    // index 사용
}

패턴 5: count_if로 조건 필터 개수

size_t error_count = std::count_if(logs.begin(), logs.end(),
     { return e.level >= 3; });

패턴 6: transform + accumulate로 가중 합

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

std::vector<int> values = {1, 2, 3, 4, 5};
std::vector<int> weights = {1, 2, 1, 2, 1};
std::vector<int> weighted(values.size());
std::transform(values.begin(), values.end(), weights.begin(),
               weighted.begin(),  { return v * w; });
int total = std::accumulate(weighted.begin(), weighted.end(), 0);

성능 비교 요약

연산알고리즘복잡도비고
정렬sortO(n log n)일반적
정렬stable_sortO(n log n)순서 유지 필요 시
부분 정렬partial_sortO(n log k)상위 k개만
선형 검색findO(n)정렬 불필요
개수count/count_ifO(n)한 번 순회
변환transformO(n)한 번 순회
집계accumulateO(n)순차
복사copy/copy_ifO(n)한 번 순회
제거remove + eraseO(n)erase-remove

13. 구현 체크리스트

프로덕션에 STL 알고리즘을 적용할 때 확인할 항목입니다.

  • find/find_if 결과 사용 전 it != end 체크
  • transform 출력 범위 크기가 입력 이상인지 확인 (또는 back_inserter + reserve)
  • accumulate 초기값 타입이 집계 결과와 일치하는지 확인 (double이면 0.0)
  • 커스텀 비교자가 strict weak ordering을 만족하는지 확인
  • remove/remove_if 후 반드시 erase로 논리적 끝 구간 제거
  • copy_if + back_inserter 사용 시 reserve로 재할당 최소화
  • empty 범위에서 나눗셈 시 0으로 나누기 방지

정리

항목설명
정렬sort, stable_sort, partial_sort — 상황에 맞게 선택
검색find, find_if, find_if_not — end 체크 필수
개수count, count_if — 한 줄 집계
변환transform — 단항/이항, 출력 범위 크기 확인
집계accumulate — 초기값 타입 일치
복사copy, copy_if — reserve로 재할당 최소화
제거remove + erase — erase-remove idiom
에러end 체크, 출력 범위 부족, 비교자 위반, erase 누락
프로덕션erase-remove, sort+unique, 파이프라인 체이닝
핵심 원칙:
  1. find 결과 사용 전 반드시 end 체크
  2. transform 출력 범위 크기 확보
  3. accumulate 초기값 타입 일치 (0.0 for double)
  4. remove/remove_if 후 반드시 erase
  5. reserve로 불필요한 재할당 방지

자주 묻는 질문 (FAQ)

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

A. 데이터 정렬, 검색, 집계, 변환, 복사, 제거 등 일상적인 컨테이너 처리에 필수입니다. for문 대신 STL 알고리즘을 쓰면 버그가 줄고 가독성이 올라갑니다.

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

A. STL 컨테이너 기초람다 표현식을 먼저 익히면 좋습니다.

Q. 더 깊이 공부하려면?

A. cppreference의 algorithm, numeric 헤더 문서와 C++20 Ranges를 참고하세요. 한 줄 요약: sort·find·count·transform·accumulate·copy·remove를 마스터하고, 흔한 에러를 피하며 프로덕션 패턴을 적용할 수 있습니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3