C++ Iterator 뜯어보기
C++ Iterator 는 어떻게 작동하는 것일까?
C++ 에서 vector 와 같은 컨테이너들을 사용하다보면 마주하는 iterator. 이들은 어떻게 구현이 되어있는 것일까? 같이 함께 뜯어보자.
Iterator
Iterator의 정의
Iterator Pattern
- 컨테이너의 구현 방법을 노출시키지 않으면서도 그 집합체 안에 들어있는 모든 항목에 접근할 수 있는 방법을 제공한다.
iterator
를 통해서 컨테이너와 알고리즘을 분리할 수 있으며, 인터페이스를 고치지 않고 새로운 순회 구조 또한 구현할 수 있다. - 다양한 컨테이너에 대해서 공통된 형태의
iterator
를 구성함으로써 더generic
한 프로그래밍을 구현하는데 큰 도움이 된다.
STL Container
- C++ 에서는
STL container
의 내부 데이터 접근/순회 방식으로iterator
를 사용한다.iterator
를 통해서 다양한 형태의 구조에 대해서 마치 포인터와 같이 데이터를 접근할 수 있으며,std::algorithm
에서도 다양한 컨테이너에 공통적으로 존재하는iterator
를 통해서 공통된 작업을 수행할 수 있다. - 예를 들어, 단순하게 연속적인 데이터를 저장하는
array
,vector
,string
과 같은 클래스의 경우 내부 데이터를 가리키는 포인터 연산을 통해서 내부 데이터를 순회하며algorithm
에 있는 다양한 템플릿 함수를 사용할 수 있겠지만,map
,set
,list
,deque
와 같이 연속적인 데이터를 가지지 않는 컨테이너의 경우 단순히 포인터 연산만으로는 내부 데이터를 순회할 수 없다. 이 때,iterator
를 통해 오버로딩된operator
를 이용하면, 내부 자료 구조를 이해하지 않더라도 포인터 연산과 유사한 방법으로 내부 데이터를 순회할 수 있다.
코드와 함께 이해하기
아래 설명에 활용되는 자료
-
자료 1) C++ 98 standard
-
자료 2) OLD_GCC libstdC++ (C++98)
libstdc++ 의 iterator의 구조
: iterator
를 끝까지 파고 들어가면 다음과 같은 구조만 덩그라니 있다.
std::iterator
는 스스로 아무것도 할 수 없다. 결국 자료구조에 따라,iterator
의 동작은 별개로 정의해야한다. 하지만,iterator
를generic
하게 사용하기 위해서는 유저가 정의한iterator
안에 위에 정의된iterator
의 요소들을 가지고 있어야한다.- 이 요소를 가지고 있기 위해서 사용자가 정의하는
iterator
는wrapping
혹은 상속을 통해서 위 구조를 내부적으로 가지고 있다. 이 과정에서iterator_traits
가 이용된다. iterator_traits
에서 들고 있는tag
를 바탕으로, 이후opreator
가 제한된다.
iterator_traits
iterator sturct
에서는 정작iterator_traits
에 대한 언급이 없다. 그러면iterator_traits
는 어디에 활용하는 것일까? 코드를 함께 살펴보자.
iterator_traits
가 등장하는 부분은iterator
를 상속하는 부분이다.iterator
를 상속받기 위해서는iterator
에 활용되는 특질인 5가지의typedef
에 관련한 부분이 필요하다. 그러나,iterator
단독으로는 이를 알아낼 수 없다.iterator
의 경우type
을template
를 통해서 가져오기 때문이다.iterator_traits
는 이 특질을 가져오기 위해서 활용된다.iterator_traits
의 경우 크게 두 가지로 구분된다. 일반적으로iterator
를 받아 사용하여iterator_traits
를 생성하는 방식과,pointer
가 들어왔을 때pointer
에 맞는 특질을 생성하는 방식이다.- 일반적인
iterator
의 경우,iterator
내부에 특질을 정의해두었을 것이다. 따라서, 내부의type definition
을 들고와서 이를 특질로 저장하면 된다. 그러나,pointer
의 경우에는 그렇지 않다.pointer
의 경우type definition
을 별도 가지고 있지않다. (int*
가iterator
에 맞는 특질을 가지고 있지 않듯…) 대신, 일반적으로 포인터를iterator
로 사용하는 경우,random_access_iterator
의 특성을 가진다. - 따라서, 포인터에 대한
iterator_traits
는 사용자가 별도 정의를 통해iterator
의 형태로 만들지 않아도, 단순히template
을 통해iterator
의 형태를 가질 수 있도록 돕는다. - 결국, C++ 98의
iterator_traits
의 경우,template
를 통해서pointer
와iterator
모두iterator
의 특질을 가질 수 있도록 유도하는 하나의 장치인 것이다.
iterator 구현 예시
- 아래
__normal_iterator
의 경우pointer
형태의iterator
의 동작을 정의하는 역할을 한다. (gnu 2.90.8)__normal_iterator
라는class
는iterator
가 아닌pointer
와 같은 형태의iterator
를 가지고 있어,iterator
의 형태로wrapping
하는데 활용된다.- 따라서
__normal_iterator
는vector
,string
과 같은 연속적 데이터를 활용하는 컨테이너에 사용된다.
-
위 코드를 잘 살펴보면,
iterator
의 멤버 변수current
가template parameter
인_Iterator type
로 정의된 것을 볼 수 있다. 그런데 결국 해당 클래스의 의도 상,pointer
가template
의_Iterator
로 할당될 것이며, 내부operation
은pointer
연산을 통해 작동하도록 정의됐다. -
두 번째 예시는
tree
구조를 사용하는stl_map
의iterator
구현이다.
- 구조를 살펴보면,
map
은Rb_tree
를 데이터 구조로 가지고 있다. 따라서, 이를 순회하기 위한Rb_tree
iterator
를 별도로 가진다. typedef typename _Rep_type::iterator iterator;
라는 부분을 통해서map
의iterator type
을 정의했으며, 이iterator
는 순회 방법에 활용하는increment()
,decrement()
와 몇가지 특질을 담는base_iterator
를 상속받는다. (여기서는std::iterator
를 별도로 상속받지 않고, 필요한 특질을 직접 정의하는 방식을 채택했다.)- 그리고,
iterator
를bidirection_iterator
에서 순회하는데 활용되는operator
인++
,—
를 정의함으로써,iterator
의 동작을 정의했다. - 결국, 사용자는
iterator
자체에 정의된operator
를 통해 직접 순회를 할 수 있으며,algorithm
에서는 아래 설명할iterator_category
를 통해서 적절하게 순회를 할 수 있을 것이다.
iterator_category
iterator_category
를 통해서 특정 범주의iterator
에 대한 동작을 별개로 오버로딩할 수 있다. 이는std::algorithm
의advance()
를 통해 구현된다. 사용자는advance
함수를 통해서 범주에 적합하게iterator
를 이동시킬 수 있다.container
를 만드는 사람의 관점에서는 내가 만든iterator
가 동작할 수 있는operator
의 범위를 제한하는 용도로 사용한다. 따라서 이에 맞게iterator_category
를 이해하고iterator
에 적절한category
를 부여하면 된다… 이 정도로 이해하면 될 것 같다.
[C++] iterator_category( 반복자 범주 )란 무엇인가?
reference
Writing a custom iterator in modern C++