개요
함수를 템플릿으로 정의하듯이 클래스도 템플릿으로 정의가 가능하다. 그리고 이렇게 정의된 템플릿을 가리켜 **클래스 템플릿(class template)**이라 하며, 이를 기반으로 컴파일러가 만들어 내는 클래스를 가리켜 **템플릿 클래스(template class)**라 한다.
템플릿 클래스는 아래의 예제와 같이 만들 수 있다.
template <typename T>
class Point
{
private :
T xpos, ypos; //다양한 자료형으로 x, y 좌표를 정의할 수 있음
public :
Point(T x = 0, T y = 0) : xpos(x), ypos(y) {}
void ShowPosition() const
{
cout<<'['<<xpos<<", "<<ypos<<']'<<endl;
}
}
int main(void)
{
Point<int> pos1(3, 4);
pos1.ShowPosition();
Point<double> pos2(2.4, 3.6);
pos2.ShowPosition();
Point<char> pos3('P', 'F');
pos3.ShowPosition();
return (0);
}
템플릿 클래스는 Point<int> 와 같이 표현하여 일반 클래스와 구분을 짓는다.
템플릿과 달리, 클래스 템플릿은 <int> <double>과 같은 자료형 정보를 생략할 수 없다.
클래스 템플릿과 클래스 선언의 분리
클래스 템플릿도 멤버 함수를 클래스 외부에 정의하는 것이 가능하다.
template <typename T>
class SimpleTemplate
{
public :
T SimpleFunc(const T& ref);
};
template <typename T>
T SimpleTemplate<T>::SimpleFunc(const T& ref) { . . . . }
위의 예제에서 SimpleTemplate<T>가 의미하는 바는 다음과 같다.
T에 대해 템플릿화 된 SimpleTemplate 클래스 템플릿
또한, 클래스 템플릿의 정의와 외부에 선언된 함수의 정의는 완전히 별개이기 때문에 각각에 대해서 문자 T가 무엇을 의미하는 지 설명해야 한다. 따라서 template<typename T>를 생략할 수는 없다.
또한 컴파일 과정에서 컴파일러는 템플릿에 관한 모든 정보를 참조해야 하기 때문에, 다음과 같이 파일을 분할하면 컴파일 에러가 난다.
- 헤더 파일 : 템플릿 클래스 정의, 멤버 함수의 원형 선언
- cpp 파일 : 템플릿 클래스 멤버 함수의 정의
- cpp 파일2 : main 문
이는 cpp 파일2에서 헤더 파일의 정보만 알 수 있고 템플릿 클래스 멤버 함수의 정의는 알 수 없기 때문으로, 다음의 방법들로 해결할 수 있다.
- 헤더파일에 템플릿의 생성자와 멤버 함수의 정의를 모두 넣는다.
- 헤더파일, cpp 파일, cpp 파일2의 파일 명을 각각
header.hppfile.cppfile2.cpp라고 할 때,file2.cpp에 다음의 구문을 추가하여야 한다.
#include "header.hpp"
#include "file.cpp" //이 문장을 추가하여 완전한 템플릿 정보를 얻을 수 있어야 함
//. . . .
클래스 템플릿의 특수화
템플릿과 마찬가지로, 클래스 템플릿도 특정 자료형에 한하여 특수화할 수 있다.
클래스 템플릿을 특수화하는 이유는 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는 다른 행동양식을 적용하기 위해서이다.
클래스 템플릿은 아래와 같은 방식으로 특수화할 수 있다.
template <typename T>
class SoSimple
{
public :
T SimpleFunc(T num) { . . . . }
};
template <>
class SoSimple<int>
{
public :
int SimpleFunc(int num) { . . . . }
}
클래스 템플릿의 부분 특수화
템플릿의 typename이 복수인 경우, 그 중 일부만 특수화를 진행하는 것을 **클래스 템플릿의 부분 특수화(class template partial specialization)**라고 한다.
/*ClassTemplatePartialSpecialization.cpp*/
#include <iostream>
using namespace std;
template <typename T1, typename T2>
class MySimple
{
public :
void WhoAreYou()
{
cout<<"size of T1: "<<sizeof(T1)<<endl;
cout<<"size of T2: "<<sizeof(T2)<<endl;
cout<<"<typename T1, typename T2>"<<endl;
}
};
template <>
class MySimple<int, double>
{
public :
void WhoAreYou()
{
cout<<"size of int : "<<sizeof(int)<<endl;
cout<<"size of double : "<<sizeof(double)<<endl;
cout<<"<int, double>"<<endl;
}
};
/*
template <typename T1>
class MySimple<T1, double>
{
public :
void WhoAreYou()
{
cout<<"size of T1: "<<sizeof(T1)<<endl;
cout<<"size of double: "<<sizeof(double)<<endl;
cout<<"<T1, double>"<<endl;
}
};
*/
int main(void)
{
MySimple<char, double> obj1;
obj1.WhoAreYou();
MySimple<int, long> obj2;
obj2.WhoAreYou();
MySimple<int, double> obj3;
obj3.WhoAreYou();
return (0);
}
참고로 템플릿 함수의 호출형식이 부분 특수화와 전체 특수화를 동시에 만족한다면 전체 특수화된 클래스를 대상으로 객체가 생성된다.
템플릿 인자
다음과 같이 템플릿 매개변수에는 변수의 선언이 올 수 있다.
template <typename T, int len>
class SimpleArray
{
private :
T arr[len];
public :
T &operator[] (int idx)
{
return arr[idx];
}
};
int main()
{
//위의 템플릿을 기반으로 다음과 같이 객체 생성 가능
SimpleArray<int, 5> i5arr;
SimpleArray<double, 7> d7arr;
}
/*
결과적으로 컴파일러에 의해 다음과 같은 객체가 생성됨
class SimpleArray<int, 5>
{
private :
int arr[5];
public :
int &operator[] (int idx) { return arr[idx]; }
};
class SimpleArray<double, 7>
{
private :
double arr[7];
public :
double &operator[] (int idx) { return arr[idx]; }
}
*/
위 예제에서 템플릿 인자를 사용하여 객체마다 멤버 변수로 존재하는 배열의 길이를 다르게 설정하였다.
생성자를 사용하여 동일한 기능을 구현할 수 있지만, 다음과 같은 차이점이 있다.
SimpleArray<int, 5>와 SimpleArray<int, 7>은 서로 다른 형(type)이다.
따라서 위 예제의 경우, 길이가 다른 두 배열 객체간의 대입 및 복사에 대한 부분을 신경쓰지 않아도 된다는 이점이 있다.
다음과 같이 템플릿 인자는 디폴트 값 지정 또한 가능하다.
template <typename T = int, int len = 7>
템플릿과 static
클래스 템플릿 내에 static 멤버변수가 존재하면 템플릿 클래스 별로 별도의 static 멤버변수를 유지하게 된다.
아래와 같이 템플릿 static 멤버변수 초기화한다.
template <typename T>
T SimpleStaticMem<T>::mem = 0;
template <> //템플릿 클래스의 static 매개변수도 특수화를 통하여 특정 자료형에 한해 다르게 초기화할 수 있다.
long SimpleStaticMem<long>::mem = 5;
참고자료