이번 시간에는 iterator와 range에 대해 살펴보겠습니다.

Iterator Basic

1. 핵심 정리

  • 반복자는 GoF 디자인 패턴에서 정의 : 복합 개체의 내부 구조에 상관 없이 순차적으로 요소에 접근하기 위한 방법을 제공 하는 것

  • STL에서의 정의 : 반복자 처럼 동작하는 모든 것은 반복자이다.(++ 연산 이동, * 연산자로 값에 접근 가능한 것)

  • STL에서는 디렉토리 내의 파일을 순회 하는 디렉토리 반복자 또한 제공함(C++17 이후)

#include <iostream>
#include <list>
#include <experimental/filesystem>
using namespace std;
using namespace std::experimental::filesystem;

int main()
{
	directory_iterator p("C:\\windows");

	cout << *p << endl;
	++p;
	cout << *p << endl;
}

2. 반복자를 다룰 때 std::begin, std::size를 사용 할 경우 배열 타입에서도 적용 가능한 범용적 코드 작성이 가능함

  • 주의 : end()로 얻는 반복자는 마지막 요소의 다음을 의미하므로 값을 꺼내 올 경우 크래시 발생
#include <iostream>
#include <list>   
#include <vector>
using namespace std;

int main()
{
	list<int> s = { 1,2,3,4,5 };
	//vector<int> s = { 1,2,3,4,5 };

	//int s[5] = { 1,2,3,4,5 };
	
	//list<int>::iterator p = s.begin();

	//auto p1 = s.begin(); //

	auto p1 = begin(s);
	int n = size(s); //  s.size();
	cout << n << endl;

	auto p2 = end(s);
	*p2 = 10; // 크래시 error;
}

3. 반복자 무효화 : 무효화 이후 접근시 크래시 발생!

  • 컨테이너의 내부 버퍼가 재할당 될 때
  • 반복자가 가르키던 요소가 제거 될 때
  • 컨테이너의 종류에 따라 무효화 조건이 다름
#include <iostream>
#include <list>   
#include <vector>
using namespace std;

int main()
{
	//vector<int> v = { 1,2,3,4,5 };
	list<int> v = { 1,2,3,4,5 };

	auto p = begin(v);

	v.resize(100); // vector는 무효화 발생, list는 발생하지 않음
				   //

	cout << *p << endl;
}

4. 반복자의 구간(Range)

  • first, last : 시작과 마지막 다음 요소를 가르키는 반복자의 쌍
  • 유효한 구간 : first부터 시작해서 ++ 연산으로 last에 도달 할 수 있는 구간
  • 빈 구간 : first == last인 경우, 빈 구간도 유효한 구간임
#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> s1 = { 1,2,3 };
	list<int> s2 = { 4,5,6 };

	auto p1 = begin(s1);
	auto p2 = end(s1);

	// [p1, p2)

	while (p1 != p2)
	{
		++p1;
	}

	list<int> s3;
	if (s3.empty()) {}

	if (begin(s3) == end(s3)) {} // empty == true
}
  • STL의 copy 알고리즘
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

int main()
{
	int x[5] = { 1, 2, 3, 4, 5 };
	
	list<int> s2 = { 0,0,0,0,0 };

	copy(x, x + 5, begin(s2));

	for (auto& n : s2)
		cout << n << ", ";
}

Iterator Category

1. Iterator Category

  • vector는 sort 알고리즘 사용가능한데, list는 사용이 불가능한 이유?
    • 컨테이너의 종류에 따라 반복자에 적용할 수 있는 연산의 종류가 다름(– 연산이 불가능한 경우 등등)
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;

int main()
{
	//vector<int> v = { 1,3,5,7,9,2,4,6,8,10 };

	list<int> v = { 1,3,5,7,9,2,4,6,8,10 };

	sort( begin(v), end(v) );

	for (auto& n : v)
		cout << n << ",";
}
  • 입력 vs 출력
    • 입력 : 반복자를 통해 요소를 읽어오는 것( = *i)
    • 출력 : 반복자를 통해 요소에 값을 쓰는 것( *i = )

2. multipass guarantee 개념

  • i1 == i2 라면 -> *i1 == *i2 혹은 ++i1 == ++i2 만족

  • 2개 이상의 반복자가 컨테이너의 요소에 동일하게 접근함을 보장

  • 컨테이너마다 지원 여부가 다름(list는 지원, stream / insert iterator는 지원 하지 않음)

#include <iostream>
#include <list>
using namespace std;

int main()
{
	list<int> s1 = { 1,2,3 };

	auto i1 = begin(s1);
	auto i2 = i1;

	if (i1 == i2)
	{
		cout << (*i1 == *i2) << endl;
		cout << (++i1 == ++i2) << endl;
	}
}

3. 반복자 카테고리 개념을 알아야 하는 이유

#include <iostream>
#include <vector>
#include <list>
#include <forward_list>
#include <algorithm>
using namespace std;

int main()
{
	vector<int> v = { 1,2,3,4,5,6,7,8,9,10 };

	// find의 1, 2 인자의 최소 요구 조건? : 입력 반복자
	auto p = find(begin(v), end(v), 5);

	reverse(begin(v), end(v));// reverse의 요구조건 : 양방향 반복자

	sort(begin(v), end(v)); // stl의 sort는 quick sort
							// sort의 요구조건 : 임의 접근 반복자.

	forward_list<int> s = { 1,2,3 };
	reverse(begin(s), end(s)); // error


	list<int> s2 = { 1,2,3 };
	sort(begin(s2), end(s2)); // error

	s2.sort(); // ok.

	vector<int> v = { 1,2,3 };

	v.sort();  // 멤버 함수 sort()가 있을까 ?
				// 없다.  일반 함수(알고리즘) 
				// sort()를 사용하면 된다.

	for (auto& n : v)
		cout << n << ", ";
}

4. tag dispatching

  • advance 구현 해보기( tag dispatching 활용 : 반복자의 카테고리에 따라 다르게 동작하도록(최적화) )
    • 모든 컨테이너는 멤버 변수에 iterator_category을 제공함
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
using namespace std;

template<typename T>
void eadvance_imp(T& p, int n, 
				  random_access_iterator_tag)
{
	cout << "random 버전" << endl;
	p = p + n;
}

template<typename T>
void eadvance_imp(T& p, int n,
				  input_iterator_tag)
{
	cout << "input 버전" << endl;
	while (n--)	++p;
}

template<typename T>
void eadvance(T& p, int n)
{
	// 반복자의 종류에 따라서 다른 함수를 선택
	eadvance_imp(p, n,
			typename T::iterator_category() );
}


int main()
{
	//list<int> s = { 1,2,3,4,5,6,7,8,9,10 };
	vector<int> s = { 1,2,3,4,5,6,7,8,9,10 };

	auto p = begin(s);

	eadvance(p, 5);

	cout << *p << endl;
}
  • 최신 문법을 활용 하면 더욱 간결하게 표현 가능함
#include <iostream>
#include <vector>
#include <list>
#include <iterator>
using namespace std;

// 1. 함수 인자를 사용한 오버로딩.
/*
template<typename T> void eadvance_imp(T& p, int n, random_access_iterator_tag)
{
	p = p + n;
}
template<typename T> void eadvance_imp(T& p, int n, input_iterator_tag)
{
	while (n--)	++p;
}
template<typename T> void eadvance(T& p, int n)
{
	eadvance_imp(p, n, typename T::iterator_category());
}
*/

// 2. C++17 if constexpr 사용.
#include <type_traits>

/*
template<typename T> void eadvance(T& p, int n)
{
	if constexpr (is_same< typename T::iterator_category, random_access_iterator_tag>::value)
	{
		p = p + n;
	}
	else
		while (n--) ++p;
}
*/
// 3. enable_if 를 사용..   SFINAE 개념 사용.

template<typename T> 
typename enable_if<is_same<typename T::iterator_category,random_access_iterator_tag>::value>::type
eadvance(T& p, int n)
{
	p = p + n;
}

template<typename T> 
typename enable_if<! is_same<typename T::iterator_category, random_access_iterator_tag>::value>::type
eadvance(T& p, int n)
{
	while (n--) ++p;
}

int main()
{
	list<int> s = { 1,2,3,4,5,6,7,8,9,10 };

	auto p = begin(s);

	eadvance(p, 5);

	cout << *p << endl;
}

Iterator traits

1. 반복자가 가르키는 타입을 찾아서 리턴하는 방법

#include <iostream>
#include <list>
#include <vector>
using namespace std;
#include <iterator>

template<typename T>
typename iterator_traits<T>::value_type
sum(T first, T last)
{
	typename iterator_traits<T>::value_type s = 0;

	while (first != last)
	{
		s = s + *first;
		++first;
	}
	return s;
}

int main()
{
	//list<int> s = { 1,2,3,4,5,6,7,8,9,10 };
	//vector<int> s = { 1,2,3,4,5,6,7,8,9,10 };

	int s[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int n = sum(begin(s), end(s));

	cout << n << endl;
}
  • C++11 이후 문법(타입 추론)을 활용한 방법(auto와 decltype)
#include <iostream>
#include <list>
#include <vector>
#include <iterator>
using namespace std;


template<typename T>
typename iterator_traits<T>::value_type
sum(T first, T last)
{
	typename 
	remove_reference<decltype(*first)>::type s = 0;

	while (first != last)
	{
		s = s + *first;
		++first;
	}
	return s;
}

int main()
{
	//list<int> s = { 1,2,3,4,5,6,7,8,9,10 };
	//vector<int> s = { 1,2,3,4,5,6,7,8,9,10 };

	int s[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int n = sum(begin(s), end(s));

	cout << n << endl;
}

Insert iterator

1. back insert iterator : 반복문 없이 한방에 여러 데이터 삽입 가능

#include <iostream>
#include <list>
#include <iterator>
using namespace std;

int main(int argc, char** argv)
{
	list<int> s = { 1, 2, 3, 4, 5};

	s.push_back(10);

	back_insert_iterator< list<int> > p( s );
	*p = 20; // s.push_back(20);

	int x[5] = { 10,20,30,40,50 };
	copy(x, x + 5, p);

	for ( auto& n : s )
		cout << n << endl; 
}
int main(int argc, char** argv)
{
	int x[5] = { 10,20,30,40,50 };
	list<int> s = { 1, 2, 3, 4, 5 };

	copy(x, x + 5, back_inserter( s) );

	for (auto& n : s)
		cout << n << endl; 
}

Iterator adapter : 기존 기능을 변경

1. reverse_iterator

int main()
{
	list<int> s = { 1,2,3,4,5,6,7,3,9,10};

	auto p1 = begin(s);
	auto p2 = end(s);

	reverse_iterator< list<int>::iterator> p3( p2 ); // p3와 p2는
													// 다른 객체
	reverse_iterator< list<int>::iterator> p4( p1 );

	auto ret1 = find(p1, p2, 3); //
	auto ret2 = find(p3, p4, 3); //

	//++p3; // --p2 처럼 동작

	cout << *p3 << endl; // 10
	++p3;
	cout << *p3 << endl; // 9
	++p3;
	cout << *p3 << endl; // 3
	--p2;
	cout << *p2 << endl; // 10
}

2. move iterator

  • 반복자 전달시 이동 생성자 호출 지원
#include <iostream>
#include <iterator>
#include <vector>
#include "People.h"
using namespace std;

int main()
{
    vector<People> v;
    v.push_back(People("A"));
    v.push_back(People("B"));
    v.push_back(People("C"));
    v.push_back(People("D"));

    cout << "------------------------" << endl;

    vector<People> v2(make_move_iterator(begin(v)),
                      make_move_iterator(end(v)));
}

Iterator helper

next, advance, distance

#include <iostream>
#include <iterator>
#include <forward_list>
using namespace std;

int main()
{
    int x[10] = { 1,2,3,4,5,6,7,8,9,10};

    forward_list<int> s = { 1,2,3,4,5,6,7,8,9,10};

    auto p1 = next(begin(s));

    advance(p1, 3); // p1 + 3;

    cout << *p1 << endl; // 5

    cout << distance( begin(s), p1) << endl; // p1 - begin(s)

    iter_swap(p1, begin(s));

    cout << *p1 << endl; // 1
}