[Effective C++] 47. 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
STL은 기본적으로 컨테이너(Container), 반복자(iterator), 알고리즘(algorithm)의 템플릿으로 구성되어 있고 그 외에 유틸리티(utility)라고 불리는 템플릿도 몇 개 들어 있습니다.
그 중에는 advance라는 이름의 템플릿이 있는데, 이 템플릿이 하는 일은 지정된 반복자를 지정된 거리만큼 이동시키는 것 입니다.
간단한 개념만 놓고 볼 때 단순하게 iter += d만 하면 될 것 같지만, += 연산을 지원하는 반복자는 임의 접근 반복자밖에 없기 때문에 그렇게 할 수 없습니다.
반복자들이 종류마다 가능한 것이 있고 불가능한 것이 있다는 점 때문에 각각의 구현을 모두 다르게 해주어야겠지요. 그렇지만 만약 임의 접근 반복자와 나머지 반복자를 구분할 수 있다면 아래와 같이 구현하는 것이 가능할 것 입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if(iter가 임의 접근 반복자이다)
{
iter += d;
}
else
{
...
// 한칸씩 이동.
...
}
}
우리는 이것을 구분할 수단이 있을까요? 지금 사용가능한 것이 바로 특성정보(traits)라는 것 입니다. 특성정보란, 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념입니다.
특성정보는 C++에 미리 정의된 문법구조가 아니며, 키워드도 아닙니다. 그냥 C++ 프로그래머들이 따르는 구현 기법이며 관례 입니다. (물론 현재의 C++에서는 type_traits이라는 헤더를 지원하고 있습니다.) 참고로 특성정보를 구현할 때 아래와 같은 관례를 지켜야 합니다.
- 특성정보는 기본제공 타입과 사용자 정의 타입에서 모두 동작해야한다.
- 특성 정보는 항상 구조체로 구현한다.
특성정보를 다루는 표준적인 방법은 특성정보를 템플릿 및 템플릿의 1개 이상의 특수화 버전에 넣는 것입니다. 반복자의 경우, 표준 라이브러리의 특성정보용 템플릿이 iterator_tratis라는 이름으로 준비되어 있습니다.
1
2
template<typename IterT>
struct iterator_traits;
위처럼 특성정보를 구현하는 데 사용한 구조체를 가리켜 특성정보 클래스라고 부릅니다.
1
2
3
4
5
6
7
8
9
10
11
12
template< ... > // 템플릿 매개 변수는 편의상 생략
class deque
{
public:
class iterator
{
public:
typedef random_iterator_tag iterator_category;
...
};
...
};
1
2
3
4
5
6
7
8
9
10
11
12
template< ... > // 템플릿 매개 변수는 편의상 생략
class list
{
public:
class iterator
{
public:
typedef bidirectional_iterator_tag iterator_category;
...
};
...
};
iterator_tratis가 동작하는 방법은 이렇습니다. iterator_traits
iterator_tratis 클래스는 이 반복자 범주를 두 부분으로 나누어 구현합니다. 첫번째 부분은 사용자 정의 반복자 타입에 대한 구현인데, 사용자 정의 반복자 타입(컨테이너의 내부 클래스로 정의된 반복자)으로 하여금 iterator_category라는 이름의 typedef 타입을 내부에 가질 것을 요구합니다.
이 내부 클래스 iterator가 지닌 중첩 typedef 타입을 동일하게 맞춰놓은 것이 iterator_tratis 입니다.
1
2
3
4
5
6
7
template<typename IterT>
struct iterator_traits
{
typedef typename IterT::iterator_category iterator_category;
// iterator_traits*::iterator_category라는 문법은 말도 안됨.
...
};
아직 끝나지 않았습니다. 위의 코드는 사용자 정의 타입에 대해서는 잘 돌아가지만, 반복자의 실제 타입이 포인터인 경우에는 전혀 동작하지 않습니다. 포인터 안에 typedef 타입이 중첩된다는 것부터가 도무지 말이 안되기 때문입니다.
여기서 iterator_tratis의 두번째 부분이 나옵니다. 반복자 포인터의 처리 부분이지요.
1
2
3
4
5
6
template<typename IterT>
struct iterator_traits<IterT*>
{
typename random_access_iterator_tag iterator_category;
...
};
포인터 타입을 지원하기 위해 포인터 타입에 대한 부분 템플릿 특수화 버전을 제공하고 있습니다.
이제 특성정보 클래스의 설계 및 구현 방법에 대해 감을 잡았을 것 입니다.
- 다른 사람이 사용하도록 열어 주고 싶은 타입 관련 정보를 확인합니다. (예를 들어 반복자라면 반복자 범주 등이 여기에 해당합니다.)
- 그 정보를 식별하기 위한 이름을 선택합니다. (예: iterator_category)
- 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전(예: iterator_tratis)을 제공합니다.
1
2
3
4
5
6
7
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))
...
}
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
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT T, std::random_access_iterator_tag)
{
iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d, std::input_iterator_tag)
{
if(d < 0)
{
}
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;
}
이제 advance가 해 줄 일은 오버로딩된 doAdvance를 호출하는 것뿐입니다. 이때 컴파일러가 오버로딩 모호성 해결을 통해 적합한 버전을 호출할 수 있도록 반복자 범주 타입 객체를 맞추어 전달해야겠지요.
1
2
3
4
5
6
7
8
9
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
doAdvance
{
iter, d,
typename std::iterator_traits<IterT>::iterator_category()
};
}
특성 정보 클래스를 어떻게 사용하는지, 마지막으로 깔끔하게 정리해봅시다.
- 작업자(worker) 역할을 맡은 함수 혹은 템플릿(예: doAdvance)을 특성정보 매개변수를 다르게 하여 오버로딩합니다. 그리고 전달되는 해당 특성정보에 맞추어 각 오버로드 버전을 구현합니다.
- 작업자를 호출하는 주작업자(master) 역할을 맡은 함수 혹은 템플릿(예: advance)을 만듭니다. 이때 특성정보 클래스에서 제공되는 정보를 넘겨서 작업자를 호출하도록 구현합니다.
End Note
- 특성정보 클래스는 컴파일 도중에 사용할 수 있는 타입 관련 정보를 만들어냅니다. 또한 특성정보 클래스는 템플릿 및 특수화 버전을 사용하여 구현합니다.
- 함수 오버로딩 기법과 결합하여 특성정보 클래스를 사용하면, 컴파일 타임에 결정되는 타입별 if…else 점검문을 구사할 수 있습니다.
Reference
- Effective C++ (Scott Meyers)
Leave a comment