템플릿과 일반화 프로그래밍 📏
안녕하세요! 두두코딩 입니다 ✋
오늘은 Effective C++ 항목 42에 대해 알아보겠습니다.
🖇 소스코드에 마우스를 올리고 copy 버튼을 누를 경우 더 쉽게 복사할 수 있습니다!
궁금한 점, 보안점 남겨주시면 성실히 답변하겠습니다. 😁
+ 감상평 댓글로 남겨주시면 힘이됩니다. 🙇
Template에서 typename 과 class
Q. 아래의 두 템플릿 선언문에 쓰인 class 와 typename의 차이점은 무엇일까?
template<class T> T Widget;
template<typename T> T Widget;
답변은 차이가 없다이다. 템플릿의 타입 매개변수를 선언할 때, typename 과 class의 뜻은 완전히 동일하다.
개인차이로 타입 매개변수를 선언하는 것을 선정해도 무방하다. 하지만, typename
을 꼭 사용해야될 때가 있는데, 그 경우를 알아보자.
typename을 사용해야되는 경우
아래와 같이 함수의 템플릿이 있다고 생각해보자.
해당 함수 같은 경우 2가지 특징이 있다.
- STL과 호환되는 컨테이너를 받아들이도록 만들어졌다.
- 컨테이너에 담기는 객체는 int로 호환이 가능하다.
template<typename C>
void print2nd(const C& container) {
if (container.size() >= 2) {
// 첫번째 원소에 대한 반복자를 얻음
C::const_iterator iter(container.begin());
++iter;
// int에 두번째 원소를 복사
int value = *iter;
std::cout << value;
}
}
iter
타입 같은 경우, C::const_iterator
타입으로 C
의 매개변수로 인해 타입이 달라지도록 설계되어져있다. 이와 같은 경우를 의존이름이라고 부른다. 의존이름이 어떤 클래스 내 중첩되어져 있는 경우를 중첩 의존 이름이라고 부른다.
템플릿 매개변수와 상관없는 변수는 비의존 이름이라고 부른다.
중첩이름이 코드 내 있을 경우 에러가 발생할 확률이 올라간다.
template <typename C>
void print2nd(const C& container) {
C::const_iterator * x;
...
}
위 코드를 통해 어떤 에러가 발생하는지 알아보자.
우리는 코드를 봤을 때, C::const_iterator
에 대한 포인터를 지역변수 x
로 선언했다는 것을 알 수 있다. 하지만 컴파일러 입장에서는 “* 포인터” 라고 생각할 수도 있고, C 라는 class 내 const_iterator
멤버 변수와 x
라는 전역변수의 “* 곱셈연산”으로 해당 문장을 해석할 수도 있다.
따라서 이를 막기 위해, typename
이라는 키워드를 해당 값 앞에 붙여줘 C 라는 매개변수가 있다는 것을 컴파일러에게 알려준다. 즉, 해당 값은 데이터가 아니라 타입이라는 것을 알려줘야한다.
template <typename C>
void print2nd(const C& container) {
typename C::const_iterator * x;
...
}
위의 코드와 같이 중첩 의존 이름 앞에는 항상 typename
을 적어줘야한다. 하지만, typename
을 사용함에서도 예외가 몇가지 존재하는데, 예외를 알아보자.
typename 예외
“typename은 중첩 의존 타입 이름 앞에 붙여 주어야 한다”는 규칙에는 2가지 예외가 존재한다.
- 상속되는 기본클래스의 리스트에 있을 경우
- 초기화 리스트 내에 기본 클래스 식별자 일 경우
template<typename T>
class Derived: public Base<T>::Nested { //상속되는 기본클래스 리스트, typename X
public :
explicit Derived(int x) : Base<T>::Nested(x) { //초기화리스트내에 기본클래스 식별자, typename X
typename Base<T>::Nested temp; // 중첩의존타입의 이름, typename O
…
}
…
};
해당 케이스 같은 경우 typename을 붙이면 안된다는 것을 알아두도록 하자.
typedef 를 활용한 typename
template을 활용한 코드 같은 경우 typename
이라는 키워드를 붙이고 사용한다면 너무 길어질 수 있다. 예를 들어, 아래의 iterator_traits
를 활용하는 코드가 있다고 가정해보자.
template<typename IterT>
void workWithIterator(IterT iter)
{
typename std::iterator_traits<IterT>::value_type temp(*iter);
...
}
위와 같은 코드가 있을 때, value_type
을 코드에서 계속 사용하기 위해서는 typename std::iterator_traits<IterT>::
을 지속적으로 적어줘야한다. 이는 코드 작성 시 에러를 유발할 수 있기 때문에 typedef
를 활용해 해당 값을 대체하도록 하는 것이 좋다.
template<typename IterT>
void workWithIterator(IterT iter)
{
typedef typename std::iterator_traits<IterT>::value_type value_type;
value_type temp(*iter);
...
}
To Sum Up
- 템플릿 매개변수 선언할 경우,
class
및typename
은 서로 바꾸어 써도 무방하다. - 중첩의존타입이름을 식별하는 용도에는 반드시
typename
을 사용하자. - 중첩의존이름이 기본클래스 리스트 또는 멤버 초기화 리스트 내 존재하는 2가지 예외를 알아두자.