Iterator
Iterator란?
C++에서 반복자는 ++
연산으로 이동하고 *
연산으로 값에 접근할 수 있는 객체. raw pointer도 반복자.
객체가 반복자 요구 조건을 만족하는지 확인하려면 input_or_output_iterator<T>
같은 concept을 사용하면 됨.
컨테이너 반복자 사용 시 주의점
컨테이너의 반복자 타입을 직접 쓰면 코드가 복잡해지고 컨테이너가 바뀔 때마다 타입도 바뀌어야 하므로 auto
사용 권장.
c.begin()
vsstd::begin()
std::begin()
은 raw array도 처리 가능- 구현은 컨테이너용과 배열용 따로 작성
template<typename C>
constexpr auto mybegin(C& c) noexcept(noexcept(c.begin())) -> decltype(c.begin())
{ return c.begin(); }
template<typename T, std::size_t SZ>
constexpr T* mybegin(T(&arr)[SZ]) noexcept
{ return arr; }
Iterator invalidation
반복자를 얻은 후에 컨테이너가 재할당되거나 수정되면 그 반복자는 더 이상 유효하지 않음.
Passing Iterator
반복자는 크기가 작고 복사 비용이 적기 때문에 함수 인자로 전달할 때 보통 값 전달(call by value)을 사용
- Cheap-to-copy types의 경우 pass by value 시 copy가 일어난다. 즉 value를 받아서 function call을 위한 stack에 넣음. 그런데 pass by reference의 경우 memory address to the stack을 push하고 called function이 그 주소에 있는 것을 fetch해야 함. 그래서 둘을 비교해보면 Cheapto-cpoy types는 그냥 pass by value로 쓰면 됨
- iterator를 참조로 받으면 dereference를 해서 iterator를 얻고 또 dereference를 해서 값에 접근하는 과정이 superfluous
- StackOverflow 논의
std::ranges::begin
std::ranges::begin은 기존 std::begin보다 안전하다.
이유는 아래와 같다
반환 타입 검사
- std::begin(c)를 하면 c가 begin함수를 가지고 있고 그걸 부르는 것
- 그런데 c가 begin을 Iterator말고 이상한 걸 리턴해도 에러가 안남
- std::ranges::begin은 반환 타입이 무조건 반복자여야한다는 조건이 있음
value category 검사
- rvalue라면 기존 begin const iterator를 반환하는데 이거는 dangling이라 undefined 동작 발생
- ranges::begin은 rvalue넣으면 에러
Range와 View
- Range:
begin
,end
로 반복자 꺼낼 수 있는 타입 - View: 실제 컨테이너를 복사하지 않고 가공된 형태로 보여주는 것 (ex:
string_view
,filter_view
등)
반복자가 가리키는 타입 얻기
- C++20:
std::iter_value_t<T>
- C++98~17:
typename T::value_type
또는typename iterator_traits<T>::value_type
사용
Iterator category
반복자는 계층 구조를 가짐 (in/output, forward, bidirectional, random access, contiguous)
STL 알고리즘은 반복자의에 대한 요구 조건이 있음
template<typename BidirIt>
void reverse(BidirIt first, BidirIt last); // -- 연산 가능한 반복자 필요
그래서 reverse를 보면 iterator category를 이용한 타입 이름을 지음
- multiple pass:
*it1 == *it2
일 때++it1
,++it2
후에도 같아야 함
istream_iterator
*
연산 시 스트림에서 값을 꺼내고 해당 값을 소비함.
Iterator operation: advance / distance / next / prev
advance
: 반복자를 특정 거리만큼 이동distance
: 반복자 간 거리 계산std::next(iter, n, last)
: 최대 범위 제한 가능
++c.begin() vs std::next(c.begin())
++c.begin()
은 rvalue에 대해 불가능. std::next(c.begin())
는 함수 인자로 넘겨지면서 lvalue가 되어 가능.
한편 vector, list의 반복자는 ++c.begin이 된다.
rvalue는 ++연산은 불가능하나 멤버함수 호출은 가능한 원리를 이용해서 아래와 같이 구성하여 iterator를 사용하게 되면 ++이 가능
class myiterator {
int* current;
public:
myiterator(int* p = nullptr) : current{p} {}
myiterator& operator++() { ++current; return *this; }
int& operator*() { return *current; }
};
Copy Container
- 대입연산자/Assign: 기존 요소 완전히 제거 후 복사
- 반복문: list는 지원 안됨. list는 operator[]가 없어서.
- copy: 기존 요소에 덮어쓰기
copy
: 반복자만 인자로 받아서 덮어씀ranges::copy
: 반복자 또는 컨테이너 자체 전달 가능
reverse_iterator
- 생성자 또는
make_reverse_iterator(it)
로 생성 가능 rbegin()
/rend()
는 내부적으로std::make_reverse_iterator(v.end())
호출- 어떤 코드를 반대로 동작시키고 싶을 때 반복자만 반대로 설정해주면 됨.
insert_iterator
- 후방 삽입:
back_insert_iterator
, ex)std::back_inserter(v)
- 전방 삽입:
front_insert_iterator
- 중간 삽입:
insert_iterator
std::vector v {1, 2, 3, 4, 5};
std::list s1{0, 0, 0, 0, 0};
std::list s2{0, 0, 0, 0, 0};
copy(v, std::front_inserter(s1));
copy(v, std::inserter(s2, s2.begin()));
- vector는
push_front
불가능. front_inserter 사용 불가
counted_iterator (C++20)
- 반복자에 횟수 제한 기능 추가
std::counted_iterator it{base_iter, 5}
- 범위 끝에는
std::default_sentinel
사용
std::counted_iterator ci{it, 5};
while (ci != std::default_sentinel) {
std::println("{}", *ci++);
}
- legacy 함수에 사용하려면
std::common_iterator<I, S>
사용 sentinel_for
concept으로 조건 검사 가능