일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- 암시적 변환
- 참조자
- more effective c++
- resource management class
- 예외
- 루아
- 오블완
- effective stl
- 언리얼
- 영화 리뷰
- 다형성
- operator new
- 상속
- Effective c++
- implicit conversion
- c++
- reference
- virtual function
- 티스토리챌린지
- Smart Pointer
- exception
- 스마트 포인터
- UE4
- 메타테이블
- 비교 함수 객체
- 반복자
- Vector
- 게임
- lua
- 영화
Archives
- Today
- Total
스토리텔링 개발자
[Effective C++] 47. 특성 정보 클래스 본문
728x90
항목 47. 타입에 대한 정보가 필요하다면 특성 정보 클래스를 사용하자
STL의 구성
- 컨테이너(container) 템플릿
- 반복자(iterator) 템플릿
- 알고리즘(algorithm) 템플릿
- 유틸리티(utility) 템플릿
- 지정된 반복자를 지정된 거리(distance)만큼 이동시킨다.
-
// iter를 d 단위만큼 양수면 전진, 음수면 후진 시킨다. templatce<typename IterT, typename DistT> void advance(IterT& iter, DistT d);
- 그냥 iter += d 만 하면 될 것 같지만..
- 모든 반복자를 지원해야 할텐데....
- += 연산자를 지원하는 반복자는 임의 접근 반복자밖에 없다.
STL 반복자의 종류
- 입력 반복자(input iterator)
- 전진만 가능하다.
- 한번에 한 칸씩만 이동한다.
- 자신이 가리키는 위치에서 읽기만 가능하다.
- 읽을 수 있는 횟수가 한 번으로 제약되어 있다.
- 입력 파일에 대한 읽기 전용 파일 포인터를 본떠서 만들었다.
- 예) istream_iterator
- 출력 반복자(output iterator)
- 전진만 가능하다.
- 한번에 한 칸씩만 이동한다.
- 자신이 가리키는 위치에서 쓰기만 가능하다.
- 쓸 수 있는 횟수가 한 번으로 제약되어 있다.
- 출력 파일에 대한 쓰기 전용 파일 포인터를 본떠서 만들었다.
- 예) ostream_iterator
- 순방향 반복자(forward iterator)
- 전진만 가능하다.
- 한번에 한 칸씩만 이동한다.
- 읽기와 쓰기를 모두 할 수 있다.
- 읽고 쓰는 횟수에 제약이 없다.
- 예) 해시 컨테이너를 가리키는 반복자
- 양방향 반복자(bidirectional iterator)
- 전진, 후진이 가능하다.
- 한번에 한 칸씩만 이동한다.
- 읽기와 쓰기를 모두 할 수 있다.
- 읽고 쓰는 회수에 제약이 없다.
- 예) set, multiset, map, multimap 등의 컨테이너의 반복자
- 임의 접근 반복자(random access iterator)
- 전진, 후진이 가능하다.
- 임의의 거리만큼 앞뒤로 이동시키는 일을 상수시간 안에 할 수 있다.
- 읽기와 쓰기를 모두 할 수 있다.
- 읽고 쓰는 회수에 제약이 없다.
- 기본 제공 포인터를 본떠서 만들었다.
- 예) vector, deque, string의 반복자
반복자 식별을 위한 태그 구조체
- 각 반복자에는 자신이 어떤 반복자인지 식별하기 위한 태그(tag) 구조체가 정의되어 있다.
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 {};
유틸리티 템플릿이 모든 반복자를 지원하게 해보자
- 최소 공통 분모(lowest-common-denominator) 전략을 쓰면 어떨까.
- 반복자를 주어진 횟수만큼 반복적으로 한 칸씩 증가 / 감소시키는 루프를 돌린다.
- 선형 시간이 걸린다는 단점이 있다.
- 상수 시간에 반복자에 접근할 수 있는 임의 접근 반복자 입장에서는 손해다.
- 아무래도 각 반복자 별로 구현을 달리하는 것이 좋을 것 같다.
- 즉, 각 타입에 대한 정보를 얻어낼 필요가 있다.
특성정보(traits)
- 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게 하는 객체를 지칭하는 개념이다.
- 요구사항
- 특성정보는 기본 타입 / 사용자 정의 타입 모두를 지원해야 한다.
- 즉, advance는 기본 포인터 타입(const char* 등)이나 기본 타입(int)으로 호출하려 할 때도 제대로 동작해야 한다.
- 뒤집어 생각하면, 어떤 타입 내에 중첩된 정보를 삽입하는 방법으로는 구현이 안 된다는 뜻이다.
- 기본 타입 내부에는 정보를 넣을 방법이 없기 때문이다.
- 즉, 특성정보는 그 타입의 외부에 존재해야 한다.
- 특성정보는 기본 타입 / 사용자 정의 타입 모두를 지원해야 한다.
특성정보를 다루는 표준적인 방법
- 해당 특성정보를 템플릿 및 그 템플릿의 1개 이상의 특수화 버전에 넣는다.
- 반복자의 경우, iterator_traits라는 이름으로 준비되어 있다.
// 반복자 타입에 대한 정보를 나타내는 템플릿
template<typename IterT>
struct iterator_traits;
- 특성정보를 구현하는 관례
- 특성정보는 항상 구조체로 구현하며, 이 특성정보 구조체를 '특성정보 클래스'라고 부른다.
특성정보 클래스의 구현 방법을 iterator_traits로 알아보자
- iteractor_traits<IterT> 안에는...
- IterT 타입 각각에 대해 iterator_category라는 이름의 typedef 타입이 선언되어 있다.
- 이렇게 선언된 typedef 타입이 바로 IterT의 반복자 태그를 가리킨다.
- iterator_traits 클래스는 반복자의 태그를 두 부분(사용자 정의 타입 / 기본제공 타입)으로 나누어 구현한다.
- 사용자 정의 반복자 타입에 대한 구현
- iterator_category라는 이름의 typedef 타입을 내부에 가질 것을 요구사항으로 둔다.
- 그리고 이 typedef 타입을 특성정보 클래스(iterator_traits)에서는
-
// deque의 반복자 태그 구현 template<...> class deque { public: class iterator { public: // 임의 접근 반복자 태그를 typedef 타입으로 가진다. typedef random_access_iterator_tag iterator_category; ... }; ... }; // list의 반복자 태그 구현 template<...> class list { class iterator { public: // 양방향 반복자 태그를 typedef 타입으로 가진다. typedef bidirectional_iterator_tag iterator_category; ... }; ... }; // 사용자 정의 타입에 대한 특성정보 클래스 template<typename IterT> struct iterator_traits { // typedef typename 에 대한 부분은 항목 42 참조 typedef typename IterT::iterator_category iterator_category; ... };
- 반복자가 포인터인 경우에 대한 구현
- 이를 처리하기 위해 특성정보 클래스는 부분 템플릿 특수화(partial template specialization) 버전을 제공한다.
-
// 기본제공 포인터에 대한 부분 템플릿 특수화 template<typename IterT> struct iterator_traits<IterT*> { typedef random_access_iterator_tag iterator_category; ... }
- 사용자 정의 반복자 타입에 대한 구현
특성 정보 클래스 설계 및 구현 방법
- 다른 사람이 사용하도록 열어주고 싶은 타입 관련 정보를 확인한다.
- 예) iterator_tag
- 그 정보를 식별하기 위한 이름을 선택한다.
- 예) iterator_category
- 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 템플릿 특수화 버전을 제공한다.
- 예) iterator_traits 템플릿
특성 정보 클래스를 사용하여 유틸리티 템플릿을 구현해보자
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))
{
...
}
}
- 해당 코드의 문제점
- 컴파일 문제
- 항목 48에서 이어서 살펴보기로 한다.
- if문은 프로그램 실행 도중에 평가되나, iterartor_traits<IterT>::iterator_category를 파악할 수 있는 건 컴파일 단계이다.
- 컴파일 도중에 할 수 있는 걸 굳이 실행 도중에 할 이유가 없다.
- 게다가 실행 코드의 크기도 커진다.
- 컴파일 문제
- 즉, 컴파일 타임에 수행하는 조건처리 구문요소가 필요하다.
- 여기에 딱 맞는 기능이 바로 C++의 오버로딩.
- 그러므로 컴파일 단에서 평가할 수 있도록 오버로드를 통해 구현해보자.
- 구현 방법
- '작업자(worker)' 역할을 맡을 함수나 함수 템플릿을 특성정보 매개변수가 다르게 하여 오버로딩한다.
- 아래의 예에서는 doAdvance가 그 역할이다.
- '주작업자(master)' 역할을 맡을 함수나 함수 템플릿을 만들고, 내부에서 작업자에 특성정보 클래스의 정보를 넘기며 호출하게 한다.
- 아래의 예에서는 Advance가 그 역할이다.
- '작업자(worker)' 역할을 맡을 함수나 함수 템플릿을 특성정보 매개변수가 다르게 하여 오버로딩한다.
// 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)
{
doAdvance(iter, d, typename std::iterator_traits<IterT>::iterator_category());
}
C++ 표준 라이브러리에서 사용하는 특성정보들
- iterator_traits
- iterator_category
- value_type(항목 42 참조)
- char_traits
- 문자 타입에 대한 정보
- numeric_limits
- 숫자 타입에 대한 정보(표현 가능한 최소값, 최대값 등)
- is_fundamental<T>
- T가 기본제공 타입인지를 알려준다.
- is_array<T>
- T가 배열타입인지 알려준다.
- is_base_of<T1, T2>
- T1이 T2와 같거나 T2의 기본 클래스인지 알려준다.
728x90
'개발 > Effective C++' 카테고리의 다른 글
[Effective C++] 49. new 처리자 (6) | 2024.07.23 |
---|---|
[Effective C++] 48. 템플릿 메타 프로그래밍 (0) | 2024.07.22 |
[Effective C++] 46. 템플릿 클래스 안에 비멤버 함수 두기 (0) | 2024.07.18 |
[Effective C++] 45. 멤버 함수 템플릿으로 암시적 변환 지원 (1) | 2024.07.17 |
[Effective C++] 44. 템플릿 코드 비대화 회피하기 (0) | 2024.07.16 |
Comments