[C++] 항목 47: 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 7. 템플릿과 일반화 프로그래밍
👉🏻 항목 47: 타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자
✅ 문제 상황: advance 함수 구현
STL은 여러 템플릿으로 구성되어 있다:
- 컨테이너(container)
 - 반복자(iterator)
 - 알고리즘(algorithm)
 - 유틸리티(utility)
 
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d);
advance 함수:
- 유틸리티 중 하나인 템플릿
 - 지정된 반복자를 지정된 거리만큼 이동시킨다
 - 우리가 구현하고자 하는 함수이다.
 
📌 STL 반복자의 5가지 종류
1. 입력 반복자 (Input Iterator)
++it(전진, 한칸 이동)- 읽기 1번 가능
 - 예: 
istream_iterator 
2. 출력 반복자 (Output Iterator)
++it(전진, 한칸 이동)- 쓰기 1번 가능
 - 예: 
ostream_iterator 
3. 순방향 반복자 (Forward Iterator)
++it(전진, 한칸 이동)- 읽기/쓰기 여러번 가능
 - 예: 
slist 
4. 양방향 반복자 (Bidirectional Iterator)
++it,-it(전후진, 한칸 이동)- 읽기/쓰기 여러번 가능
 - 예: 
list,set,multiset,map,multimap 
5. 임의 접근 반복자 (Random Access Iterator)
+, ,+=,=,[]등 지원 (임의 거리 이동을 상수 시간에 가능!)- 읽기/쓰기 여러번 가능
 - 예: 
vector,deque,string 
📌 반복자 태그 구조체
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 {};
핵심:
- C++ 표준 라이브러리에는 반복자를 식별하기 위한 “태그(tag) 구조체”가 정의되어 있다
 - 각각 is-a 관계로 상속이 되어있다 (이후, 이유를 알 수 있음)
 
⚠️ 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)에 대해 알아보자.
✅ 특성 정보(Traits)란?
특성 정보(traits): 컴파일 도중 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념
요구사항:
- 기본제공 타입과 사용자 정의 타입에서 모두 특성 정보를 지원해야 한다
 - 기본 제공 타입인 
포인터및int에도 동작하여야 함 
문제:
- 하지만, 
포인터나int와 같은 타입에 특성 정보를 넣을 수 없다 - 즉, 특성 정보는 타입의 외부에 존재해야 함
 
해결:
- 템플릿 및 그 템플릿의 1개 이상의 특수화 버전에 넣어야 한다
 - 반복자의 경우, 표준 라이브러리의 특성정보용 템플릿이 
iterator_traits이름으로 있다 
📌 iterator_traits 구조
// 반복자 타입에 대한 정보를 나타내는 템플릿
template<typename IterT>
struct iterator_traits {
	// 현재 중요한 것은 여기
	typedef typename IterT::iterator_category iterator_category;
	// 이 외에 다른 변수들도 있다.
	typedef typename IterT::value_type value_type;
	...
};
특성정보 클래스:
- 위와 같이 특성 정보를 구현하는데 사용한 구조체를 ‘특성정보 클래스’라고 부른다
 iterator_traits<vector<int>::iterator>::value_type temp(*iter)와 같은 형태로
변수에 넣어 사용할 수 있다
✅ iterator_traits의 두 가지 구현
iterator_traits 클래스는 반복자 범주를 두 부분으로 나누어 구현한다:
- 사용자 정의 반복자 타입
 - 포인터 타입
 
1. 사용자 정의 반복자 타입 구현
// deque(임의 접근 반복자를 가짐)
template<...> // 편의상 생략
class deque {
public:
	class iterator {
	public:
		typedef random_access_iterator_tag iterator_category;
		...
	};
	...
};
// list(양방향 반복자를 가짐)
template<...>
class list {
public:
	class iterator {
	public:
		typedef bidirectional_iterator_tag iterator_category;
		...
	};
	...
};
핵심:
- iterator_traits와 연결하기 위해서, 일련의 작업이 필요하다
 - 사용자 정의 반복자 타입 내에 iterator_category라는 이름의 typedef 타입을 두어야 한다
 
동작 방법 상세
// deque(임의 접근 반복자를 가짐)
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;
	...
};
int main() {
	deque<int> dq = {1,2,3};
	deque<int>::iterator iter = dq.begin();
	typename iterator_traits<deque<int>::iterator>::iterator_category temp(*iter);
}
단계별 동작:
deque내의random_access_iterator_tag가iterator_category라는 별칭을 가지게 됨iterator_traits는 템플릿 매개변수로deque를 받게되고,
deque::iterator_category가iterator_category라는 별칭으로 다시 바뀜- 이제 
iter의iterator_category를temp에 저장할 수 있음 
2. 포인터 타입 구현
// 기본제공 포인터 타입에 대한 부분 템플릿 특수화
template<typename IterT>
struct iterator_traits<IterT*> {
	typedef random_access_iterator_tag iterator_category;
	...
};
핵심:
- 포인터 타입의 반복자를 지원하기 위해, 
iterator_traits는 부분 템플릿 특수화 버전을 제공한다 
📋 특성정보 클래스의 설계 및 구현 방법
- 다른 사람이 사용하도록 열어 주고 싶은 타입 관련 정보를 확인한다
    
- 반복자라면 반복자 범주
 
 - 그 정보를 식별하기 위한 이름을 선택한다
    
- 예: 
iterator_category 
 - 예: 
 - 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전을 제공한다
 
⚠️ 잘못된 시도: typeid 사용
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
	if(typeid(typename iterator_traits<IterT>::iterator_category)
		== typeid(random_access_iterator_tag)
	) {
		iter += d;
	} else {
		if(d >= 0) { while(d--) ++iter; }
		else { while(d++) --iter; }
	}
}
문제점:
- 이 코드는 컴파일이 안된다 (이유는 항목 48에서)
 - 더 큰 문제:
    
IterT의 타입과iterator_traits<IterT>::iterator_category는 컴파일 도중 파악된다- if문은 런타임에 평가된다
 - 즉, 주어진 타입에 대한 평가를 컴파일 도중 해야한다
 
 
✅ 올바른 해결: 함수 오버로딩
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
	random_access_iterator_tag
) {
	iter += d;
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
	bidirectional_iterator_tag
) {
	if(d >= 0) { while(d--) ++iter; }
	else { while(d++) --iter; }
}
template<typename IterT, typename DistT>
void doAdvance(IterT& iter, DistT d,
	input_iterator_tag
) {
	if(d < 0) { throw out_of_range("Negative distance"); }
	else { while(d--) ++iter; }
}
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
	doAdvance(iter, d,
		typename iterator_traits<IterT>::iterator_category()
	);
}
핵심 원리:
forward_iterator_tag는input_iterator_tag를 상속했다- 즉, 
input_iterator_tag를 매개변수로 받는doAdvance는, 순방향 반복자도 받을 수 있다 - 반복자 태그 사이의 상속이 왜 되어있었던 것인지 여기서 알 수 있다
 - advance에서 오버로딩된 
doAdvance를 호출함으로써, 이전의 문제를 해결하였다 
📋 특성정보 클래스 구현 방법 정리
1. 작업자(worker) 역할 설정
- 작업자 역할을 맡을 함수 혹은 함수 템플릿(예: doAdvance)을 특성정보 매개변수를 다르게 하여 오버로딩한다
 - 전달되는 해당 특성정보에 맞추어 각 오버로드 버전을 구현한다
 
2. 주작업자(master) 역할 설정
- 작업자를 호출하는 주작업자 역할을 맡을 함수 혹은 함수 템플릿(예: advance)를 만든다
 - 특성정보 클래스에서 제공되는 정보를 넘겨서 작업자를 호출하도록 구현한다
 
📌 TR1의 추가 특성정보 클래스
TR1(항목 52 참조)가 도입되며, 특성정보 클래스가 상당수 추가되었다:
is_fundamental<T>: T가 기본제공 타입인지 알려준다is_array<T>: T가 배열 타입인지 알려준다is_base_of<T1, T2>: T1이 T2와 같거나 T2의 기본 클래스인지 알려준다
🧐 정리
- 특성정보 클래스는 컴파일 도중에 사용할 수 있는 타입 관련 정보를 만들어 낸다.
또한 특성정보 클래스는 템플릿 및 템플릿 특수 버전을 사용하여 구현한다. - 함수 오버로딩 기법과 결합하여 특성정보 클래스를 사용하면,
컴파일 타임에 결정되는 타입별 if…else 점검문을 구사할 수 있다. - 특성정보 클래스는 작업자 함수(worker)와 주작업자 함수(master)의 조합으로 구현한다.
주작업자는 특성정보를 전달하고, 작업자는 오버로딩을 통해 적절한 버전을 선택한다. - 반복자 태그의 상속 관계는 함수 오버로딩을 통한 자동 타입 선택을 가능하게 한다.
이것이 태그 구조체들이 is-a 관계로 설계된 이유다. 
댓글남기기