성능과 가독성이라는 두 가지 측면에서 STL 알고리즘을 사용한다. 다 다루진 않습니다~
STL(Standard Template Library)는 일종의 데이터 타입을 모은 세트이며, 컨테이너이자 표준 C++에 포함된 알고리즘이다. STL의 알고리즘은 컨테이너가 아니라 반복자 에 대해 동작한다.
모든 알고리즘은 하나의 반복자 쌍이 필요하며, 범위에서 첫 번째 요소를 가리키는 지점과 마지막 요소의 끝 부분을 가리키는 두 번째 지점을 말한다.
auto vec = std::vectpr<std::string>{
"a", "b", "c", "d"};
auto first = vec.begin();
auto last = vec.end();

보다시피 last는 "d" 요소의 뒷부분을 가리키고 있다.
STL 알고리즘은 특정 범위의 요소를 수정 만 할 수 있다. 해당 요소는 컨테이너에 추가 , 삭제 되지 않는다.
예를 들어 std::remove()나 std::unique()의 경우 실제로는 컨테이너에서 요소를 삭제하는게 아니라,
제거하려는 요소가 뒷부분에 배치되도록 재정렬하는 것이다.
auto vec = std::vector<int>{1, 2, 3, 4};
auto new_en = std::remove(
vec.begin(), vec.end(), 3
);

실제로 제거는 vec.erase(new_end, vec_end());로 실행
출력 반복자에 데이터를 기록하는 알고리즘은 출력하려면 이미 할당된 데이터가 필요하다.
컨테이너가 비었는데 출력을 위한 알고리즘을 사용하면 그 프로그램은 크래시(crash)된다. 고장난다는 거지
이런 문제를 막으려면 둘 중 하나를 수행해야 한다.
struct Month {
// 검색할 때 사용하는 동일 여부의 판단 동작
auto operator==(const Month& m) const {
return height_ == m.height_; }
int height_{};
};
// std::find는 operator==를 사용
auto birth_month = *std::find(month.begin(), month.end(), Month{9});
모드 알고리즘은 요소를 이동시킬 때 std::swap이나 std::move를 사용하지만 이동 생성자와 이동 할당이 noexcept로 명시돼 있는 경우에만 해당된다.
직접 for 반복문을 만들기보다는 알고리즘을 사용하는 것이 좋다. 가독성이 좋고 오류가 발생할 가능성도 줄며, 코드를 다시 찾아 수정하고 최적화하기 쉽다.
※ for문으로 가득한 코드 베이스는 모든 알고리즘이 비슷~하게 보여서 읽기가 어렵다.
앞에서 STL 알고리즘 라이브러리는 한 쌍의 반복자 파라미터가 필요하다고 설명했다. 그래서 살짝 복잡한 구문을 갖고 있다. 하지만 범위 라이브러리는 반복자 대신 범위를 파라미터로 사용한다.
| 반복자로 동작하는 STL 알고리즘 | 컨테이너로 동작하는 범위 라이브러리 |
|---|---|
| std::sort(a.begin(), a.end()); std::count(a.begin(), a.end(), 12); | ranges::sor(a); ranges::count(a, 12); |
이처럼 보다 간단해진다.
범위 라이브러리는 세 가지 유형의 동작인 액션, 뷰, 알고리즘 으로 구성돼 있다
근데 핵심은 뷰! 뷰를 포함하여 범위 라이브러리의 세 가지 유형을 알아보자.
※ 예를 들어 이런 게 있다.
ranges::action::sort; // 이건 액션
ranges::view::unique; // 이건 뷰
입출력을 위해 컨테이너를 사용하고, 새롭게 수정된 컨테이너를 반환한다.
액션은 컨테이너의 크기를 변경한다.
1.2 에서 std::remove를 하더라도 실제로 컨테이너의 요소가 지워진 건 아니라고 했다.
반면, 액션은 ranges::action::remove를 할 경우 컨테이너에서 요소를 제거한 작아진 컨테이너를 반환한다. unique도 마찬가지.
액션은 데이터를 변경하기 때문에 뷰에서 동작하지 않는다.
액션을 사용해서 컨테이너를 변경하려면 다음 중 하나를 선택해야 한다.
| 구문 접근법 | |
|---|---|
| 준비 단계 : 코드 초기화 | namespace ra = ranges::action; auto get(){return std::vector{1, 3, 5, 7};} auto above_5 = [](auto v){ return v >= 5; }; |
| get()을 직접 사용 | auto vals = get() | ra::remove_if(above_5); // vals는 "1, 3" |
| std::move로 만든 r-value | auto vals = get(); vals = std::move(vals) | ra::remove_if(above_5); // vals는 "1, 3" |
| |= 연산자를 사용한 변경 컨테이너 | auto vals = get(); vals |= ra:: remove_if(above_5); // vals는 "1, 3" |
액션은 입력 컨테이너로 작업하고 변경된 버전을 반환하는 반면에, 뷰는 반복 연산 시점에 변경된 컨테이너처럼 보이는 프록시 뷰를 반환할 뿐이다.
컨테이너는 전혀 변경되지 않으며, 모든 처리 과정은 반복자 내에서 수행된다.
액션은 컨테이너를 변환하므로 타입을 변환할 수 없지만, 뷰는 어떤 타입의 뷰로도 변환할 수 있다.
namespace ra = ranges::action;
namespace rv = ranges::view;
auto get_number() { return std::vector<int>{1,3,5,7}; }
// 액션은 타입을 변환할 수 없다.
auto strings = get_number() | ra::transform([](auto v){
return std::to_string(v); // 컴파일되지 않는다!
});
// 뷰는 타입을 변환할 수 있다.
auto ints = get_numbers();
auto str_view = ints | rv::transform([](auto v){
return std::to_string(v);
});
뷰나 변경된 컨테이너를 반환하지 않는 범위 라이브러리의 알고리즘은 단순히 알고리즘으로만 참조된다. ranges::count 나 ranges::any_of 등이 해당한다. 얘네는 반복자 한 쌍 대신 범위를 사용하는 경우를 제외하고는 STL에서 변형이 없는 알고리즘으로서 정확하게 동작한다.