KoreanFoodie's Study
Effective C++ | 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자 본문
Effective C++ | 항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
GoldGiver 2022. 10. 25. 16:33
C++ 프로그래머의 필독서이자 바이블인, 스콧 마이어스의 Modern Effective C++ 를 읽고 기억할 내용을 요약하고 있습니다. 꼭 읽어보시길 추천드립니다!
항목 47 : 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
핵심 :
1. 특성정보 클래스는 컴파일 도중에 사용할 수 있는 타입 관련 정보를 만들어낸다. 또한 특성정보 클래스는 템플릿 및 템플릿 특수 버전을 사용하여 구현한다.
2. 함수 오버로딩 기법과 결합하여 특성정보 클래스를 사용하면, 컴파일 타임에 결정되는 타입별 if...else 점검문을 구사할 수 있다.
STL 에는 container, iterator, algorithm 말고도 utility 라고 불리는 템플릿도 몇 개 들어 있다. 이들 중 advance 라는, 지정된 반복자를 지정된 거리(distance) 만큼 이동시키는 녀석을 보자.
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);
사실 iterator 는 다섯 종류가 있다 : (contiguous iterator 는 일단 제외하자. C++20 부터의 기능이다)
- 입력 반복자(input iterator) : 전진만 가능, 한 번에 한 칸씩 이동, 가리키는 위치에서 읽기만 가능, 읽을 수 있는 횟수는 한 번
- 출력 반복자(output iterator) : 입력 반복자와 비슷하지만 출력용이라 쓰기만 가능
- 순방향 반복자(forward iterator) : 읽기 쓰기 여러 번 가능, 전진만 가능
- 양방향 반복자(bidirectional iterator) : 읽기 쓰기 여러 번 가능, 전진/후진 가능
- 임의 접근 반복자(random access iterator) : 반복자를 임의의 거리만큼 이동 가능. 제일 강력
struct input_iterator_tag { };
struct output_iterator_tag { };
struct forward_iterator_tag : public input_iterator_tag { };
struct bidirectional_iterator_tag : public forward_iterator_tag { };
struct random_access_iterator_tag : public bidirectional_iterator_tag { };
struct contiguous_iterator_tag : public random_access_iterator_tag { }; // (since C++20)
위의 다섯 반복자 범주 각각을 식별하는 데 쓰이는 "태그(tag) 구조체" 가 C++ 표준 라이브러리에 정의되어 있다.
이제 다시 advance 로 돌아와서, 구현을 해 보자.
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
if (iter 가 임의 접근 반복자이다)
{
iter += d;
}
else
{
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
위 부분의 코드가 제대로 동작하려면 iter 부분이 임의 접근 반복자인지 판단할 수 있어야 한다. 이때 사용하는 것이 바로 특성정보(traits) 이다. 특성정보란, 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념이다.
특성정보는 C++ 에 미리 정의된 문법구조가 아니며, 키워드도 아니다. 그냥 프로그래머들이 따르는 구현 기법이자 관례이다. 특성정보는 기본 제공 타입에 대해서 쓸 수 있어야 하는데, 이는 중첩된 정보 등으로는 구현이 안된다는 말과도 같다. 결국, 어떤 타입의 특성정보는 그 타입의 외부에 존재하는 것이어야 한다.
// 반복자 타입에 대한 정보를 나타내는 템플릿
template<typename IterT>
struct iterator_traits;
보다시피, iterator_traits 는 구조체 템플릿이다. 관례에 따라, 특성정보는 항상 구조체로 구현하는 것으로 굳어져 있다. 또한 이런 구조체를 가리켜 '특성정보 클래스'라고 부른다.
이는 특성 정보는 두 부분으로 나뉘어져 있다. 먼저, 사용자 정의 반복자 타입으로 하여금 iterator_category 라는 typedef 타입이 선언될 것을 요구사항으로 둔다. 또한 이 iterator 클래스가 내부에 지닌 typedef 타입을 앵무새처럼 똑같이 재생한 것이 iterator_traits 이다.
// 매개변수는 편의상 생략...
template<...>
class deque
{
public:
class iterator
{
public:
typedef random_access_iterator_tag iterator_category;
};
};
// 반복자 타입에 대한 정보를 나타내는 템플릿
template<typename IterT>
struct iterator_traits
{
typedef typename IterT::iterator_category iterator_category;
};
// 포인터 타입의 반복자 지원
// 부분 템플릿 특수화(partial template specialization) 버전
template<typename IterT>
struct iterator_traits<IterT*>
{
typedef random_access_iterator_tag iterator_category;
};
이제 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))
{
// 임의 접근 반복자일때의 동작 수행
}
...
}
if ... else 문의 처리는 런타임에 수행하지만, 사실 우리는 위의 타입 체킹이 컴파일 타임에 이루어졌으면 한다(추가로, 이 코드는 컴파일 문제가 있는데, 이는 항목 48에서 다루겠다).
오버로딩을 사용하면 우리가 원하는 것을 얻을 수 있다.
// 임의 접근 반복자
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_iterator_tag)
{
if (d >= 0) { while (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;
}
이제 advance 를 수정하자. 오버로딩된 doAdvance 를 호출하기만 하면 된다.
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
// iter 의 반복자 타입에 맞는 doAdvance 의 오버로드 버전을 호출한다
doAdvance
(
iter, d,
typename std::iterator_traits<IterT>::iterator_category()
);
}
정리하면, 특성정보 클래스 사용은 다음과 같은 2-step 으로 이루어져 있다.
- "작업자(worker)" 역할을 맡을 함수 혹은 함수 템플릿(e.g. doAdvance)을 특성정보 매개변수를 다르게 하여 오버로딩한다(오버로드 버전 구현).
- 작업자를 호출하는 "주작업자(master)" 역할을 맡을 함수 혹은 함수 템플릿(e.g. advance) 를 만들고, 특성정보 클래스에서 제공되는 정보를 넘겨서 작업자를 호출하도록 구현한다.
'Tutorials > C++ : Advanced' 카테고리의 다른 글
Effective C++ | 항목 49 : new 처리자의 동작 원리를 제대로 이해하자 (0) | 2022.10.25 |
---|---|
Effective C++ | 항목 48 : 템플릿 메타프로그래밍, 하지 않겠는가? (0) | 2022.10.25 |
Effective C++ | 항목 46 : 타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자 (0) | 2022.10.25 |
Effective C++ | 항목 45 : "호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방! (0) | 2022.10.25 |
Effective C++ | 항목 44 : 매개변수에 독립적인 코드는 템플릿으로부터 분리시키자 (0) | 2022.10.25 |