Class Template
클래스 템플릿
- 함수 템플릿에서 확장된 개념으로, C++의 \(\texttt{class}\)에도 템플릿을 적용할 수 있다.
(기본적인 \(\texttt{template}\) Syntax는 윗 줄의 함수 템플릿 링크를 참조)
- 템플릿 클래스 선언과 템플릿 메서드 정의는 근본적으로 클래스 정의와 메서드의 정의가 아니다. 클래스 템플릿은 그 자체로 하나의 틀을 제공하는 도구이며, 특정 데이터형이 배정되어 Instantiation(구체화)가 이루어져야 비로소 하나의 클래스 정의와 멤버 함수의 정의가 생성되는 것이다.
※ \(\texttt{export}\) 키워드 : 클래스 템플릿과 템플릿 멤버 함수들은 일반 클래스를 정의할 때와 달리, 분할 컴파일(별개의 파일로 분리하는 작업)이 기본적으로 불가능한데, 이를 가능하게 하는 키워드이다. C++11부터는 더이상 지원하지 않는 키워드이지만, 미래에 사용될 가능성을 고려하여 \(\texttt{export}\) 키워드는 예약어로 분류되어 있다.
Syntax (문법)
- 함수 템플릿과 마찬가지로, 템플릿 클래스는 아래 코드와 같은 \(\texttt{template}\) 제한자를 클래스 선언 앞에 붙여야 한다.
template <typename T> // template <class T> 또한 가능!
class Stack {
...
};
※ \(\texttt{typename}\) 키워드 대신, 익숙한 \(\texttt{class}\) 키워드를 사용해도 문제는 없지만, 클래스 키워드인 \(\texttt{class}\)와 혼선이 생길 수 있어, \(\texttt{typename}\) 표기가 권고된다.
- \(\texttt{template}\) 메서드 정의 시에는, 템플릿 데이터형을 표시하는 홑화살괄호(<>)를 클래스 제한자 앞에 붙여야한다.
template <typename T>
bool Stack<T>::push(const T& item) {
...
}
※ 클래스 선언 안에서 메서드를 정의하면, \(\texttt{template}\) 제한자와 \(\texttt{class}\) 제한자를 생략할 수 있으며, 클래스 선언 안에서 정의가 선언된 메서드는 인라인 함수로 간주된다.
※ \(\texttt{T}\)와 같은 포괄적인 데이터형 식별자를 Type Parameter(데이터형 매개변수)라고 한다. 데이터형 매개변수에는 수치값이 아닌, 데이터형 자체가 대입된다.
- 템플릿 클래스 Instantiation & 객체 생성
// 템플릿 클래스 객체 선언의 일반형
className<dataType> objectName;
// 실 사용 예시
Stack<int> kernels; // int형 클래스가 구체화되고 kernels 객체가 생성된다.
- 템플릿 선언 내에서의 약식 표기와 템플릿 선언 외부에서의 정식 표기는 \(\texttt{<typeParameter>}\) 의 유무의 차이이다.
template <typename Type>
class Stack {
...
Stack& operator=(const Stack& st); // 템플릿 내부에서의 약식 표기; <Type> 없음
...
};
Stack<Type>& Stack<Type>::operator=(const Stack<Type>& st) { // 템플릿 외부에서의 정식 표기; <Type> 있음
...
}
- Default Type Parameter(디폴트 데이터형 매개변수) : 템플릿 매개변수에도 디폴트 값을 지정할 수 있다.
template <class T1, Class T2 = int>
class Topo {
...
};
Topo<double, double> m1; // T1은 double형, T2도 double형으로 지정된다.
Topo<double> m2; // T1은 double형, T2는 디폴트 값(int형)으로 지정된다.
※ 이는 클래스 템플릿에만 한정된 특징이며, 함수 템플릿 매개변수에는 디폴트 값을 지정할 수 없다.
단, 클래스 템플릿과 함수 템플릿 모두 수식 매개변수에 대해서는 디폴트 값을 지정할 수 있다.
Expression Argument (수식 매개변수)
- 템플릿 정의에서는 Type Parameter(데이터형 매개변수)가 아닌, Expression Argument(수식 매개변수) 또한 수용할 수 있다
// 수식 매개변수(Expression Argument, Non-Type Argument)를 사용하는 템플릿 예시
template <class T, int n>
class ArrayTP {
private:
T ar[n];
...
};
정수형, 열거형, 참조, 포인터가 수식 매개변수가 될 수 있다
ex) 수식 매개변수로써, \(\texttt{double n}\)은 허용되지 않지만, \(\texttt{double& n}\)은 가능하다.
- 템플릿 코드에서는 수식 매개변수의 값을 변경하거나 주솟값을 얻는 작업등은 할 수 없다.
ex) 수식 매개변수 \(\texttt{int n}\)이 있을 때, \(\texttt{n++}\)나 \(\texttt{&n}\)과 같은 표현은 허용되지 않는다.
- 템플릿이 구체화되어, 객체를 생성할 때, 수식 매개변수에 해당하는 값으로는 꼭 상수 수식이어야 한다. 수식 매개변수로 변수는 허용되지 않는다.
- 수식 매개변수를 통해, 사용자로부터 배열의 크기를 전달받는 경우, 수식 매개변수를 통해 사이즈가 정해진 배열은 자동 변수들과 같이 스택 메모리에 저장되기 때문에 크기가 작은 배열을 많이 사용하는 경우에선, \(\texttt{new}\)를 통해 Heap 영역에 배치되는 것보다 실행 속도 측면에서 유리하다.
- 템플릿 클래스는 하나의 데이터형에 대해 구체화가 요구되면, 그 데이터형에 맞춰진 클래스 정의가 하나 탄생하게 된다. 여기에서 확장된 개념으로, 수식 매개변수에 하나의 값이 주어져서 그에 대한 구체화가 요구되면, 그 값에 맞춰진 클래스 정의가 하나 탄생하게 된다.
즉, \(\texttt{int n}\)이라는 수식 매개변수가 있을 때, \(\texttt{n}\) 값에 1이 전달되면 \(\texttt{n = 1}\)인 경우의 구체화가 이루어지고, \(\texttt{n}\) 값에 2가 전달되면, \(\texttt{n = 2}\) 인 경우의 구체화가 이루어지는 비효율적인 상황이 발생하게 된다.
template <class T, int n>
class ArrayTP {
...
};
...
ArrayTP<double, 12> eggweights; <double, 12>에 맞는 클래스 정의가 생성된다.
ArrayTP<double, 13> donuts; <double, 13>에 맞는 클래스 정의가 생성된다.
// 그러나, 아래와 같은 생성자 접근 방식의 선언은 한 클래스 정의만 생성되어 효율적이다.
ArrayTP<double> eggweights(12);
ArrayTP<double> donuts(13);
// 물론, 위 클래스 정의와는 다른 구조로 뒷받침되어야 할 것이다.
※ 생성자 접근 방식의 다른 장점으로는, 배열 크기를 멤버로 저장함으로싸, 한 배열을 다른 크기의 배열에 대입하는 것을 가능하게 정의하거나 배열의 크기를 실행시간동안 조절할 수 있는 기능들을 구현할 수 있게한다는 것이다.
Inheritance for Template Class (템플릿 클래스의 상속)
- 템플릿 클래스 또한, 일반적인 클래스와 다를 바 없이 C++ 상속 기능을 적용시킬 수 있다.
// Ex. 템플릿 클래스의 상속 예시
template <typename T>
class Father {
...
};
template <typename Type>
class Son : public Father<type> { // 상속
...
};
template <typename Tp>
class Daughter {
Father<Tp> ar; // 컨테인먼트
...
};
- 템플릿 매개변수로, 다른 템플릿 클래스가 올 때, \(\texttt{<<}\) 연산자와의 혼동을 피하기 위해 같은 기호의 홑화살괄호(<>) 간에 하나의 공백을 주는 것을 C++98에서는 문법으로 지정했었는데, C++11부터는 이러한 문법 사항을 제거했다.
// >> 연산자와의 혼선 예방 예시 (C++11부터는 상관없음)
Father < Son<int> > asi; // 홑화살괄호 사이에 하나의 WhiteSpace가 존재한다. (C++98의 요구사항)
Father<Son<int>> asi; // C++11부터는 신경 쓸 필요 없다.
Using a Template Recursively (템플릿의 재귀)
- 템플릿에도 재귀를 사용할 수 있다.
// Ex. 템플릿의 재귀적 사용
template <typename T, int n>
class ArrayTP { // ArrayTP는 배열을 관리하는 사용자 정의 클래스라 가정하자.
...
};
ArrayTP< ArrayTP<int, 5>, 10> twodee;
// 위 구문은 아래 구문처럼 동작한다. (ArrayTP가 배열을 관리하는 클래스라는 가정 하에)
int twodee[10][5];
Template Specialization (템플릿 특수화)
- Specialization(특수화) = Implicit Instantiation(암시적 구체화) + Explicit Instantiation(명시적 구체화) + Explicit Specialization(명시적 특수화)
- Instantiation(구체화) & Specialization(특수화)에 관한 기초적인 개념은 이전 포스트를 참조하자.
- 클래스 템플릿에서도 명시적 구체화를 수행할 수 있다.
// Ex. 템플릿의 명시적 구체화 예시
template <typename T, int n>
class ArrayTP {
...
};
tamplate class ArrayTP<std::string, 100>; // 명시적 구체화
// ArrayTP<std::string, 100> 클래스 정의를 생성한다.
// 해당 클래스의 객체가 생성되지 않았더라도, 클래스 정의를 먼저 만들어놓는다.
- Specialized Template Class(특수화 템플릿 클래스)는 특정 데이터형이나 특정 매개변수 값에 특수화된 클래스 템플릿 정의를 아래와 같은 Syntax로 선언할 수 있다.
// Ex. 특수화된 클래스 템플릿 정의의 일반형
template <> class className<specializedTypeName> {
...
};
// 실 사용 예시 (ArrayTP 템플릿 클래스가 기정의되어있다 가정하자.)
template <> class ArrayTP<char*> {
...
};
- Partial Specialization(부분적 특수화)는 특수화 템플릿 클래스에서 다수의 템플릿 매개변수 중 몇몇 템플릿 매개변수는 특수화 값을 지정하지 않고, 그대로 놔두는 형식의 특수화 방식이다.
// 포괄 템플릿, 부분적 특수화, 완전 특수화 예시
// 포괄적인 템플릿
Template <class T1, class T2>
class ArrayTP {
...
};
// 부분적 명시적 특수화
// (T1은 템플릿 매개변수로 기존과 같이 모든 데이터형을 받아들이고, T2에 한해서 T2가 int일 경우 우선시 되는 특수화 클래스를 정의함)
template <class T1> class ArrayTP<T1, int> {
...
};
// 완전 명시적 특수화 (홑화살괄호 내부가 완전히 비워져있음)
template <> class ArrayTP<char*, int> {
...
};
- 포인터 매개변수들을 위한 특별한 버전의 템플릿 클래스를 제공하여 포인터 변수들을 위한 명시적 특수화를 정의할 수도 있다.
// Ex. 포인터들을 위한 명시적 특수화 템플릿 클래스 예시
// 포괄적인 템플릿 클래스
template <class T>
class ArrayTP {
...
};
// 포인터 템플릿 매개변수들을 위한 명시적 특수화 버전
template <class T*>
class ArrayTP {
...
};
...
ArrayTP<char> Ar1; // 포괄적인 템플릿 클래스를 이용한다.
ArrayTP<char*> Ar2; // T* 특수화 템플릿을 이용한다.
- 부분적 특수화를 응용해서, 다양한 경우의 매개변수 입력에 대응할 수 있다.
// 부분적 특수화 응용 예시
// 일반적인 포괄적 템플릿
template <class T1, class T2, class T3>
class Trio { ... };
// T3를 T2로 설정하는 부분적 특수화
template <class T1, class T2> class Trio<T1, T2, T2> { ... };
// T3와 T2를 T1*로 설정하는 부분적 특수화
template <class T1> class Trio<T1, T1*, T1*> { ... };
Member Template (멤버 템플릿)
- 템플릿은 구조체, 클래스, 템플릿 클래스의 멤버가 될 수 있다. (컨테인먼트가 가능하다.)
// Ex. 멤버 템플릿 예시
template <typename T>
class beta {
private:
template<typename V> // 템플릿 클래스를 컨테인먼트
class hold {
private:
V val;
public:
hold(V v = 0) : val(v) { }
void show () const { cout << val << endl; }
V value () const { return val; }
};
hold<T> q; // 템플릿 객체를 컨테인먼트
hold<int> n; // 템플릿 객체를 컨테인먼트
public:
beta(T t, int i) : q(t), n(i) { }
template <typename U> // 템플릿 함수를 컨테인먼트
U blab (U u, T t) { return (n.Value() + q.Value()) * u / t; }
void Show () const { q.show(); n.show(); }
};
Template As Parameters (매개변수로서의 템플릿)
- 템플릿은 그 자체가 템플릿인 매개변수를 가질 수 있다.
// Ex. 템플릿 매개변수 예시
// template<typename T> class : 템플릿 매개변수의 데이터형 부분
// Thing : 템플릿 매개변수의 이름
template <template<typename T> class Thing>
class ArrayTP {
private:
Thing<int> s1;
Thing<double> s2;
...
};
// Ex. 템플릿 매개변수의 또 다른 예시
template <template <typename T> class Thing, typename U, typename V>
class Crab {
private:
Thing<U> s1;
Thing<V> s2;
...
};
template <typename T>
class Stack {
...
};
...
Crab<Stack, int, double> nebula; // T = Stack, U = int, V = double 로 대체된다.
Template Class and Friend (템플릿 클래스와 프렌드 함수)
- 템플릿 클래스도 프렌드 함수를 Grant 할 수 있으며, 템플릿의 프렌드를 아래 3가지로 분류할 수 있다.
1. Non-template friend(템플릿이 아닌 프렌드)
// Ex. 템플릿이 아닌 프렌드 예시
template <class T>
class HasFriend {
friend void counts(); // 모든 HasFriend 구체화들에 대해 프렌드이다.
... // 즉, T가 어떤 데이터형이든 상관없다.
}
// 여기서, counts()는 멤버가 아니므로 객체에 의해 호출되지 않는다.
// 따라서, counts()는 일반 객체에는 접근하지 못하고, 전역 객체에 접근 가능하며,
// 전역 포인터를 이용해서 전역이 아닌 일반 객체에 접근할 수 있다.
// 또한, 클래스의 static 멤버에도 접근할 수 있다.
- 또한 클래스 객체를 매개변수로 지정할 때, 반드시 템플릿 데이터형도 명시되어야 한다.
// 매개변수로서의 템플릿 클래스 객체 주의사항 예시
friend void report(HasFriend&);
// 위 구문은 불가능한 구문이다.
// HasFriend<short>& 와 같이, 데이터형이 지정되어 있어야 한다.
// 템플릿 클래스 매개변수를 제공하려면, 하나의 특수화를 나타내어야 함을 의미한다.
// 올바른 구현 예시
template <class T>
class HasFriend {
friend void report(HasFriend<T>&);
...
};
2. Bound template friend(바운드 템플릿 프렌드) : 클래스가 구체화될 때 클래스의 데이터형에 의해 프렌드의 데이터형이 결정되는 형태
// Ex. 바운드 템플릿 프렌드 함수 예시
template <typename T>
class HasFriend {
friend void report(HasFriend<T>&); // 이 프렌드 함수는 short형과 int형에 대비되었다.
...
};
void report(HasFriend<short>& parameters) {...}; // short형에 대비한 명시적 특수화
void report(HasFriend<int>& parameters) {...}; // int형에 대비한 명시적 특수화
// 위에서 보다시피, 명시적 특수화된 외부의 프렌드 함수 곁에는 template 제한자가 없음에 유의하자.
※ 명시적 특수화가 정의된 외부 프렌드 함수에는 \(\texttt{template}\) 제한자가 붙지 않음에 유의하자.
(왜냐하면, 그 함수들은 본질적으로 템플릿이 아니기 때문이다.)
- 프렌드 함수들을 템플릿으로 만드는 방법
// Ex. 프렌드 함수들을 템플릿으로 만드는 방법
// 1단계. 클래스 정의 앞에 템플릿 함수의 원형을 선언한다.
template <typename T> void counts();
template <typename T> void report(T&);
// 2단계. 클래스 내부에 프렌드로 선언한다.
template <typename TT>
class HasFriendT {
...
friend void counts<TT>();
friend void report<HasFriendT<TT>>(HasFriendT<TT>&);
// report()의 경우, report의 매개변수 HasFriendT<TT>&로 부터 템플릿 데이터형 매개변수를 유추할 수 있기 때문에,
// friend void report<>(HasFriendT<TT>&); 처럼 홑화살괄호 내부 내용을 생략하여 표기할 수도 있다.
// counts()함수는 매개변수가 없기 때문에 홑화살괄호 내부의 TT를 생략하면 안되는 것이다.
};
// 3단계. 프렌드 정의를 제공한다.
template <typename T>
void counts() { ... }
template <typename T>
void report(T& hf) { ... }
3. Unbound template friend(언바운드 템플릿 프렌드) : 프렌드의 모든 특수화가 그 클래스의 각 특수화에 대해 프렌드들인 형태
- 템플릿을 클래스 안에 선언함으로써, 언바운드 프렌드 함수를 생성할 수 있다.
(바운드 프렌드 함수는 엄연히, 클래스 밖에 선언된다.)
- 모든 함수 특수화는 모든 클래스 특수화에 대해 프렌드이다.
// 언바운드 프렌드 함수 정의 및 사용 예시
template <typename T>
class ManyFriend {
private:
T item;
public:
ManyFriend(const T& i) : item(i) { }
template <typename C, typename D> // 템플릿 함수가 클래스 내부에서 선언됨
friend void show2(C&, D&);
};
template <typename C, typename D>
void show2(C& c, D& d) {
cout << c.item << ", " << d.item << endl;
}
int main() {
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(10.5);
show2(hfi1, hfi2);
show2(hfdb, hf1);
return 0;
}
Class Template
클래스 템플릿
- 함수 템플릿에서 확장된 개념으로, C++의 class에도 템플릿을 적용할 수 있다.
(기본적인 template Syntax는 윗 줄의 함수 템플릿 링크를 참조)
- 템플릿 클래스 선언과 템플릿 메서드 정의는 근본적으로 클래스 정의와 메서드의 정의가 아니다. 클래스 템플릿은 그 자체로 하나의 틀을 제공하는 도구이며, 특정 데이터형이 배정되어 Instantiation(구체화)가 이루어져야 비로소 하나의 클래스 정의와 멤버 함수의 정의가 생성되는 것이다.
※ export 키워드 : 클래스 템플릿과 템플릿 멤버 함수들은 일반 클래스를 정의할 때와 달리, 분할 컴파일(별개의 파일로 분리하는 작업)이 기본적으로 불가능한데, 이를 가능하게 하는 키워드이다. C++11부터는 더이상 지원하지 않는 키워드이지만, 미래에 사용될 가능성을 고려하여 export 키워드는 예약어로 분류되어 있다.
Syntax (문법)
- 함수 템플릿과 마찬가지로, 템플릿 클래스는 아래 코드와 같은 template 제한자를 클래스 선언 앞에 붙여야 한다.
template <typename T> // template <class T> 또한 가능!
class Stack {
...
};
※ typename 키워드 대신, 익숙한 class 키워드를 사용해도 문제는 없지만, 클래스 키워드인 class와 혼선이 생길 수 있어, typename 표기가 권고된다.
- template 메서드 정의 시에는, 템플릿 데이터형을 표시하는 홑화살괄호(<>)를 클래스 제한자 앞에 붙여야한다.
template <typename T>
bool Stack<T>::push(const T& item) {
...
}
※ 클래스 선언 안에서 메서드를 정의하면, template 제한자와 class 제한자를 생략할 수 있으며, 클래스 선언 안에서 정의가 선언된 메서드는 인라인 함수로 간주된다.
※ T와 같은 포괄적인 데이터형 식별자를 Type Parameter(데이터형 매개변수)라고 한다. 데이터형 매개변수에는 수치값이 아닌, 데이터형 자체가 대입된다.
- 템플릿 클래스 Instantiation & 객체 생성
// 템플릿 클래스 객체 선언의 일반형
className<dataType> objectName;
// 실 사용 예시
Stack<int> kernels; // int형 클래스가 구체화되고 kernels 객체가 생성된다.
- 템플릿 선언 내에서의 약식 표기와 템플릿 선언 외부에서의 정식 표기는 <typeParameter> 의 유무의 차이이다.
template <typename Type>
class Stack {
...
Stack& operator=(const Stack& st); // 템플릿 내부에서의 약식 표기; <Type> 없음
...
};
Stack<Type>& Stack<Type>::operator=(const Stack<Type>& st) { // 템플릿 외부에서의 정식 표기; <Type> 있음
...
}
- Default Type Parameter(디폴트 데이터형 매개변수) : 템플릿 매개변수에도 디폴트 값을 지정할 수 있다.
template <class T1, Class T2 = int>
class Topo {
...
};
Topo<double, double> m1; // T1은 double형, T2도 double형으로 지정된다.
Topo<double> m2; // T1은 double형, T2는 디폴트 값(int형)으로 지정된다.
※ 이는 클래스 템플릿에만 한정된 특징이며, 함수 템플릿 매개변수에는 디폴트 값을 지정할 수 없다.
단, 클래스 템플릿과 함수 템플릿 모두 수식 매개변수에 대해서는 디폴트 값을 지정할 수 있다.
Expression Argument (수식 매개변수)
- 템플릿 정의에서는 Type Parameter(데이터형 매개변수)가 아닌, Expression Argument(수식 매개변수) 또한 수용할 수 있다
// 수식 매개변수(Expression Argument, Non-Type Argument)를 사용하는 템플릿 예시
template <class T, int n>
class ArrayTP {
private:
T ar[n];
...
};
정수형, 열거형, 참조, 포인터가 수식 매개변수가 될 수 있다
ex) 수식 매개변수로써, double n은 허용되지 않지만, double& n은 가능하다.
- 템플릿 코드에서는 수식 매개변수의 값을 변경하거나 주솟값을 얻는 작업등은 할 수 없다.
ex) 수식 매개변수 int n이 있을 때, n++나 &n과 같은 표현은 허용되지 않는다.
- 템플릿이 구체화되어, 객체를 생성할 때, 수식 매개변수에 해당하는 값으로는 꼭 상수 수식이어야 한다. 수식 매개변수로 변수는 허용되지 않는다.
- 수식 매개변수를 통해, 사용자로부터 배열의 크기를 전달받는 경우, 수식 매개변수를 통해 사이즈가 정해진 배열은 자동 변수들과 같이 스택 메모리에 저장되기 때문에 크기가 작은 배열을 많이 사용하는 경우에선, new를 통해 Heap 영역에 배치되는 것보다 실행 속도 측면에서 유리하다.
- 템플릿 클래스는 하나의 데이터형에 대해 구체화가 요구되면, 그 데이터형에 맞춰진 클래스 정의가 하나 탄생하게 된다. 여기에서 확장된 개념으로, 수식 매개변수에 하나의 값이 주어져서 그에 대한 구체화가 요구되면, 그 값에 맞춰진 클래스 정의가 하나 탄생하게 된다.
즉, int n이라는 수식 매개변수가 있을 때, n 값에 1이 전달되면 n = 1인 경우의 구체화가 이루어지고, n 값에 2가 전달되면, n = 2 인 경우의 구체화가 이루어지는 비효율적인 상황이 발생하게 된다.
template <class T, int n>
class ArrayTP {
...
};
...
ArrayTP<double, 12> eggweights; <double, 12>에 맞는 클래스 정의가 생성된다.
ArrayTP<double, 13> donuts; <double, 13>에 맞는 클래스 정의가 생성된다.
// 그러나, 아래와 같은 생성자 접근 방식의 선언은 한 클래스 정의만 생성되어 효율적이다.
ArrayTP<double> eggweights(12);
ArrayTP<double> donuts(13);
// 물론, 위 클래스 정의와는 다른 구조로 뒷받침되어야 할 것이다.
※ 생성자 접근 방식의 다른 장점으로는, 배열 크기를 멤버로 저장함으로싸, 한 배열을 다른 크기의 배열에 대입하는 것을 가능하게 정의하거나 배열의 크기를 실행시간동안 조절할 수 있는 기능들을 구현할 수 있게한다는 것이다.
Inheritance for Template Class (템플릿 클래스의 상속)
- 템플릿 클래스 또한, 일반적인 클래스와 다를 바 없이 C++ 상속 기능을 적용시킬 수 있다.
// Ex. 템플릿 클래스의 상속 예시
template <typename T>
class Father {
...
};
template <typename Type>
class Son : public Father<type> { // 상속
...
};
template <typename Tp>
class Daughter {
Father<Tp> ar; // 컨테인먼트
...
};
- 템플릿 매개변수로, 다른 템플릿 클래스가 올 때, << 연산자와의 혼동을 피하기 위해 같은 기호의 홑화살괄호(<>) 간에 하나의 공백을 주는 것을 C++98에서는 문법으로 지정했었는데, C++11부터는 이러한 문법 사항을 제거했다.
// >> 연산자와의 혼선 예방 예시 (C++11부터는 상관없음)
Father < Son<int> > asi; // 홑화살괄호 사이에 하나의 WhiteSpace가 존재한다. (C++98의 요구사항)
Father<Son<int>> asi; // C++11부터는 신경 쓸 필요 없다.
Using a Template Recursively (템플릿의 재귀)
- 템플릿에도 재귀를 사용할 수 있다.
// Ex. 템플릿의 재귀적 사용
template <typename T, int n>
class ArrayTP { // ArrayTP는 배열을 관리하는 사용자 정의 클래스라 가정하자.
...
};
ArrayTP< ArrayTP<int, 5>, 10> twodee;
// 위 구문은 아래 구문처럼 동작한다. (ArrayTP가 배열을 관리하는 클래스라는 가정 하에)
int twodee[10][5];
Template Specialization (템플릿 특수화)
- Specialization(특수화) = Implicit Instantiation(암시적 구체화) + Explicit Instantiation(명시적 구체화) + Explicit Specialization(명시적 특수화)
- Instantiation(구체화) & Specialization(특수화)에 관한 기초적인 개념은 이전 포스트를 참조하자.
- 클래스 템플릿에서도 명시적 구체화를 수행할 수 있다.
// Ex. 템플릿의 명시적 구체화 예시
template <typename T, int n>
class ArrayTP {
...
};
tamplate class ArrayTP<std::string, 100>; // 명시적 구체화
// ArrayTP<std::string, 100> 클래스 정의를 생성한다.
// 해당 클래스의 객체가 생성되지 않았더라도, 클래스 정의를 먼저 만들어놓는다.
- Specialized Template Class(특수화 템플릿 클래스)는 특정 데이터형이나 특정 매개변수 값에 특수화된 클래스 템플릿 정의를 아래와 같은 Syntax로 선언할 수 있다.
// Ex. 특수화된 클래스 템플릿 정의의 일반형
template <> class className<specializedTypeName> {
...
};
// 실 사용 예시 (ArrayTP 템플릿 클래스가 기정의되어있다 가정하자.)
template <> class ArrayTP<char*> {
...
};
- Partial Specialization(부분적 특수화)는 특수화 템플릿 클래스에서 다수의 템플릿 매개변수 중 몇몇 템플릿 매개변수는 특수화 값을 지정하지 않고, 그대로 놔두는 형식의 특수화 방식이다.
// 포괄 템플릿, 부분적 특수화, 완전 특수화 예시
// 포괄적인 템플릿
Template <class T1, class T2>
class ArrayTP {
...
};
// 부분적 명시적 특수화
// (T1은 템플릿 매개변수로 기존과 같이 모든 데이터형을 받아들이고, T2에 한해서 T2가 int일 경우 우선시 되는 특수화 클래스를 정의함)
template <class T1> class ArrayTP<T1, int> {
...
};
// 완전 명시적 특수화 (홑화살괄호 내부가 완전히 비워져있음)
template <> class ArrayTP<char*, int> {
...
};
- 포인터 매개변수들을 위한 특별한 버전의 템플릿 클래스를 제공하여 포인터 변수들을 위한 명시적 특수화를 정의할 수도 있다.
// Ex. 포인터들을 위한 명시적 특수화 템플릿 클래스 예시
// 포괄적인 템플릿 클래스
template <class T>
class ArrayTP {
...
};
// 포인터 템플릿 매개변수들을 위한 명시적 특수화 버전
template <class T*>
class ArrayTP {
...
};
...
ArrayTP<char> Ar1; // 포괄적인 템플릿 클래스를 이용한다.
ArrayTP<char*> Ar2; // T* 특수화 템플릿을 이용한다.
- 부분적 특수화를 응용해서, 다양한 경우의 매개변수 입력에 대응할 수 있다.
// 부분적 특수화 응용 예시
// 일반적인 포괄적 템플릿
template <class T1, class T2, class T3>
class Trio { ... };
// T3를 T2로 설정하는 부분적 특수화
template <class T1, class T2> class Trio<T1, T2, T2> { ... };
// T3와 T2를 T1*로 설정하는 부분적 특수화
template <class T1> class Trio<T1, T1*, T1*> { ... };
Member Template (멤버 템플릿)
- 템플릿은 구조체, 클래스, 템플릿 클래스의 멤버가 될 수 있다. (컨테인먼트가 가능하다.)
// Ex. 멤버 템플릿 예시
template <typename T>
class beta {
private:
template<typename V> // 템플릿 클래스를 컨테인먼트
class hold {
private:
V val;
public:
hold(V v = 0) : val(v) { }
void show () const { cout << val << endl; }
V value () const { return val; }
};
hold<T> q; // 템플릿 객체를 컨테인먼트
hold<int> n; // 템플릿 객체를 컨테인먼트
public:
beta(T t, int i) : q(t), n(i) { }
template <typename U> // 템플릿 함수를 컨테인먼트
U blab (U u, T t) { return (n.Value() + q.Value()) * u / t; }
void Show () const { q.show(); n.show(); }
};
Template As Parameters (매개변수로서의 템플릿)
- 템플릿은 그 자체가 템플릿인 매개변수를 가질 수 있다.
// Ex. 템플릿 매개변수 예시
// template<typename T> class : 템플릿 매개변수의 데이터형 부분
// Thing : 템플릿 매개변수의 이름
template <template<typename T> class Thing>
class ArrayTP {
private:
Thing<int> s1;
Thing<double> s2;
...
};
// Ex. 템플릿 매개변수의 또 다른 예시
template <template <typename T> class Thing, typename U, typename V>
class Crab {
private:
Thing<U> s1;
Thing<V> s2;
...
};
template <typename T>
class Stack {
...
};
...
Crab<Stack, int, double> nebula; // T = Stack, U = int, V = double 로 대체된다.
Template Class and Friend (템플릿 클래스와 프렌드 함수)
- 템플릿 클래스도 프렌드 함수를 Grant 할 수 있으며, 템플릿의 프렌드를 아래 3가지로 분류할 수 있다.
1. Non-template friend(템플릿이 아닌 프렌드)
// Ex. 템플릿이 아닌 프렌드 예시
template <class T>
class HasFriend {
friend void counts(); // 모든 HasFriend 구체화들에 대해 프렌드이다.
... // 즉, T가 어떤 데이터형이든 상관없다.
}
// 여기서, counts()는 멤버가 아니므로 객체에 의해 호출되지 않는다.
// 따라서, counts()는 일반 객체에는 접근하지 못하고, 전역 객체에 접근 가능하며,
// 전역 포인터를 이용해서 전역이 아닌 일반 객체에 접근할 수 있다.
// 또한, 클래스의 static 멤버에도 접근할 수 있다.
- 또한 클래스 객체를 매개변수로 지정할 때, 반드시 템플릿 데이터형도 명시되어야 한다.
// 매개변수로서의 템플릿 클래스 객체 주의사항 예시
friend void report(HasFriend&);
// 위 구문은 불가능한 구문이다.
// HasFriend<short>& 와 같이, 데이터형이 지정되어 있어야 한다.
// 템플릿 클래스 매개변수를 제공하려면, 하나의 특수화를 나타내어야 함을 의미한다.
// 올바른 구현 예시
template <class T>
class HasFriend {
friend void report(HasFriend<T>&);
...
};
2. Bound template friend(바운드 템플릿 프렌드) : 클래스가 구체화될 때 클래스의 데이터형에 의해 프렌드의 데이터형이 결정되는 형태
// Ex. 바운드 템플릿 프렌드 함수 예시
template <typename T>
class HasFriend {
friend void report(HasFriend<T>&); // 이 프렌드 함수는 short형과 int형에 대비되었다.
...
};
void report(HasFriend<short>& parameters) {...}; // short형에 대비한 명시적 특수화
void report(HasFriend<int>& parameters) {...}; // int형에 대비한 명시적 특수화
// 위에서 보다시피, 명시적 특수화된 외부의 프렌드 함수 곁에는 template 제한자가 없음에 유의하자.
※ 명시적 특수화가 정의된 외부 프렌드 함수에는 template 제한자가 붙지 않음에 유의하자.
(왜냐하면, 그 함수들은 본질적으로 템플릿이 아니기 때문이다.)
- 프렌드 함수들을 템플릿으로 만드는 방법
// Ex. 프렌드 함수들을 템플릿으로 만드는 방법
// 1단계. 클래스 정의 앞에 템플릿 함수의 원형을 선언한다.
template <typename T> void counts();
template <typename T> void report(T&);
// 2단계. 클래스 내부에 프렌드로 선언한다.
template <typename TT>
class HasFriendT {
...
friend void counts<TT>();
friend void report<HasFriendT<TT>>(HasFriendT<TT>&);
// report()의 경우, report의 매개변수 HasFriendT<TT>&로 부터 템플릿 데이터형 매개변수를 유추할 수 있기 때문에,
// friend void report<>(HasFriendT<TT>&); 처럼 홑화살괄호 내부 내용을 생략하여 표기할 수도 있다.
// counts()함수는 매개변수가 없기 때문에 홑화살괄호 내부의 TT를 생략하면 안되는 것이다.
};
// 3단계. 프렌드 정의를 제공한다.
template <typename T>
void counts() { ... }
template <typename T>
void report(T& hf) { ... }
3. Unbound template friend(언바운드 템플릿 프렌드) : 프렌드의 모든 특수화가 그 클래스의 각 특수화에 대해 프렌드들인 형태
- 템플릿을 클래스 안에 선언함으로써, 언바운드 프렌드 함수를 생성할 수 있다.
(바운드 프렌드 함수는 엄연히, 클래스 밖에 선언된다.)
- 모든 함수 특수화는 모든 클래스 특수화에 대해 프렌드이다.
// 언바운드 프렌드 함수 정의 및 사용 예시
template <typename T>
class ManyFriend {
private:
T item;
public:
ManyFriend(const T& i) : item(i) { }
template <typename C, typename D> // 템플릿 함수가 클래스 내부에서 선언됨
friend void show2(C&, D&);
};
template <typename C, typename D>
void show2(C& c, D& d) {
cout << c.item << ", " << d.item << endl;
}
int main() {
ManyFriend<int> hfi1(10);
ManyFriend<int> hfi2(20);
ManyFriend<double> hfdb(10.5);
show2(hfi1, hfi2);
show2(hfdb, hf1);
return 0;
}