일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 | 31 |
- EffectiveC++
- 일반화복사생성자
- most vexing parse
- uniqueptr
- 이른진단
- 암시적인터페이스
- rcsp
- operator=
- 스택풀림
- private상속
- effectivec++.
- Directx9
- c++
- 멤버함수템플릿
- 가상기본클래스
- 도우미함수
- RAII
- 상수객체참조
- 암시적변환
- sharedptr
- 게임프로그래밍
- 템플릿
- contextswitching
- fvf
- 복사함수
- 교차dll문제
- 부분복사
- 해골책
- 제네릭프로그래밍
- 정점버퍼
- Today
- Total
성공할 게임개발자
[Effective c++] [7. 템플릿과 제네릭 프로그래밍] 47. 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 본문
[Effective c++] [7. 템플릿과 제네릭 프로그래밍] 47. 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
fn000 2025. 6. 29. 21:22STL 반복자는 지원하는 연산에 따라 5개의 범주로 나뉜다.
입력반복자
읽기전용
단방향 전진만 가능
std::istream_iterator<int> it(std::cin);
int x = *it; // 읽기 가능
출력반복자
쓰기전용
단방향 전진만 가능
std::ostream_iterator<int> out(std::cout, " ");
*out = 10; // 쓰기 가능
순방향 반복자
읽기, 쓰기 가능
여러번 읽기 가능
단방향 전진
std::istream_iterator<int> it(std::cin);
int x = *it; // 읽기 가능
양방향 반복자
순방향에서 뒤로 갈 수 있는 기능 추가한 것
대표 컨테이너 : set, multiset, map, multimap, list
std::list<int>::iterator it;
++it; --it;
임의 접근 반복자
양방향 반복자에 반복자 산술 연산 수행 기능 추가
산술연산
인덱스 접근
비교연산
가장 빠르고 강력함
대표 컨테이너 : vector, deque, string
std::vector<int>::iterator it;
it = it + 3;
int x = it[1];
5개의 반복자 범주 식별을 위한 태그 구조체
struct input_iterator_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidriectional_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};
구조체의 상속 관계를 보면 is-a 인데, 예를들면 모든 순방향 반복자는 입력 반복자도 되기 때문이다.
advance 예제
반복자들이 종류마다 가능한 것이 있고 안되는 것이 있다는걸 안 이상. 구현을 달리해야한다.
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
// 임의 접근 반복자에 대해서는 반복자 산술 연산
if( iter가 임의 접근 반복자일 경우 )
{
iter += d;
}
// 다른 종류의 반복자에 대해서는 ++ 혹은 -- 연산의 반복 호출을 사용. n 시간 복잡도
else
{
if(d >= 0) { while(d--) ++iter;}
else { while(d++) --iter;}
}
}
위의 코드가 제대로 작동하려면 iter 부분이 임의 접근 반복자인지 판단할 수 있어야한다. 즉, iter 타입인 IterT가 임의 접근 반복자 타입인지를 알아야한다는 것이다. 이걸 알게 해주는 존재가 특성정보이고 특성정보란, 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념이다.
특성정보는 C++에 정의된 문법 구조가 아니라 C++프로그래머들이 따르는 구현기법이며 관례이다. 특성정보가 되려면 몇 가지 요구사항이 지켜져야 하는데, 특성정보는 기본제공 타입과 사용자 정의 타입에서 모두 돌아가야한다는 점이 그 중 하나이다.
특성 정보를 다루는 표준적인 방법은 해당 특성 정보를 템플릿 및 그 템플릿의 1개 이상의 특수화 버전에 넣는 것이다. 반복자의 경우, 표준 라이브러리의 특성정보용 템플릿이 iterator_traits라는 이름으로 준비되어 있다.
// 반복자 타입에 대한 정보를 나타내는 템플릿
template<typename IterT>
struct iterator_traits;
iterator_traits는 구조체(클래스) 템플릿이다. 관례에 따라 항상 구조체로 구현한다. 이 구조체를 "특성정보 클래스" 라고 부른다.
iterator_traits는 구조체(클래스)가 동작하는 방법
iterator_traits<IterT> 안에 IterT 타입 각각에 대해 iterator_category라는 이름의 typedef타입이 선언되어 있다. 이렇게 선언된 typedef 타입이 바로 IterT의 반복자 범주를 가리키는 것
iterator_traits 클래스는 이 반복자 범주를 두 부분으로 나눈다. 첫번짼 사용자 정의 반복자 타입에 대한 구현이다. 사용자 정의 반복자 타입으로 iterator_category라는 이름의 typedef 타입을 내부에 가질 것을 요구사항으로 둔다. 이 typedef 타입은 해당 테그 구조체에 대응되어야한다.
deque클래스에 쓸 수 있는 반복자
// deque
template<...>
class deque
{
public:
class iterator
{
public:
// 임의 접근 반복자 태그를 typedef 타입으로
typedef random_access_iterator_tag iterator_category;
...
};
...
};
list 클래스에 쓸 수 있는 반복자
양방향 반복자를 사용한다.
// list의 반복자 태그 구현
template<...>
class list
{
class iterator
{
public:
// 양방향 반복자 태그를 typedef 타입으로
typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
사용자 정의타입의 경우
// 사용자 정의 타입에 대한 특성정보 클래스
template<typename IterT>
struct iterator_traits
{
// typedef typename는 이중 의존이라 사용
typedef typename IterT::iterator_category iterator_category;
...
};
하지만 반복자의 실제 타입이 포인터인 경우에는 안 돌아간다. iterator_traits 구현의 두 번째 부분은 반복자가 포인터인 경우의 처리
부분 템플릿 특수화
포인터 타입의 반복자를 지원하기 위함.
// 기본제공 포인터 타입에 대한 부분 템플릿 특수화
template<typename IterT>
struct iterator_traits<IterT*>
{
typedef random_access_iterator_tag iterator_category;
...
};
따라서 특성정보 클래스의 설계 및 구현 방법 프로세스는 다음과 같다.
1. 다른 사람이 사요하도록 열어주고 싶은 타입 관련 정보를 확인
2. 정보를 식별하기 위한 이름을 선택한다. (iterator_category)
3. 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전을 제공한다.(ex. iterator_traits)
이걸 토대로 advance의 의사코드를 다음과 같이 설정한다.
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if(typeid(typename std::iterator_traits<IterT>::iterator_category) ==
typeid(std::random_access_iterator_tag))
...
}
하지만 문제점이 있는데 IterT의 타입은 컴파일 도중에 파악되어야 하는데 if-else문을 쓰면 런타임에 검사된다. 따라서 이걸 컴파일에 해결하기 위하여 컴파일타임에 if-else 처럼 쓸 오버로딩을 사용한다. doadvance를 오버로드 함수로 만든다.
// 반복자에 대해서는 이 구현
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::random_access_iterator_tag)
{
iter += d;
}
// 양방향 반복자에 대해서는 이 구현
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::bidirectional_access_iterator_tag)
{
if(d >= 0) { whild (d--) ++iter; }
else { while (d++) --iter; }
}
// 입력반복자에 대해서는 이 구현
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
if(d < 0)
{
// 미정의 동작 방지.. 순방향 반복자나 입력반복자를 음수 거리만큼 이동하는 어처구니 없는..
throw std::out_of_range("Negative distance");
}
while (d--) ++iter;
}
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
// iter 반복자 범주에 적합한 doAdvance의 오버로드 버전 호출(컴파일타임)
doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}
중요포인트!
특성정보 클래스는 컴파일 도중에 사용할 수 있는 타입 관련 정보를 만들어낸다. 또한 특성 정보 클래스는 템플릿 및 템플릿 특수 버전을 사용하여 구현
함수 오버로딩 기법과 결합하여 특성정보 클래스를 사용하면, 컴파일 타임에 결정되는 타입별 if-else 점검문을 구사할 수 있음..
솔직히 이번내용 잘 이해가 안간다. 꾸준히 보면서 와닿을 때 까지 다시 보는걸로...