함수나 클래스를 정의할 때 다룰 자료형(Data Types)을 구체적으로 지정하지 않고 일반화된 형식(Generic Types)으로 지정한다. 이 때 일반화된 형식으로 지정한 자료형을 Template Parameters라고 한다
함수의 리턴 자료형, 파라미터 자료형, 지역변수 자료형만 다르고 함수의 기능이 같은 경우 여러개의 함수를 선언할 필요 없이 함수 템플릿을 사용하면 된다.
클래스 안에서 사용되는 변수의 자료형을 일반화하여 선언하려면 함수 템플릿이 아니라 클래스 템플릿을 사용하면 된다.
코딩시 템플릿으로 정의된 함수를 호출할 때나 템플릿을 정의된 클래스의 인스턴스를 생성하는 코드 부분에서 Template Parameter에 구체적인 자료형을 지정해주면 컴파일러가 해당 함수나 클래스의 템플릿을 사용하여 구체적인 함수나 클래스를 생성하고 컴파일하게 된다.
자료형이 다르다는 이유로 여러개의 함수를 생성하거나 오버로드하는 불편을 덜고 컴파일러가 대신하도록 할 수 있다.
C++의 STL에 포함된 자료구조나 알고리듬을 잘 이해하고 이용하려면 템플릿에 대한 개념이 필수적이다
함수 템플릿 선언 형식
class, typename 이라는 키워드를 사용할 수 있고 2개의 의미는 완전히 동일하다. 새로운 표준에서 typename 이라는 키워드를 도입했다
template <class identifier> function_declaration;
template <typename identifier> function_declaration;
함수 템플릿 선언 예
template <typename myType>
myType GetMax (myType a, myType b)
{
return (a>b?a:b);
}
함수 템플릿을 사용하여 구체적 자료형을 지정하고 함수를 호출하는 형식
function_name <type> (parameters);
함수 템플릿의 템플릿 파라미터(myType)에 실제 자료형을 지정하고 함수를 호출하는 예
int max_int = GetMax<int>(2, 3);
double max_double = GetMax<double>(2.1, 3.2);
함수 템플릿을 실제로 사용하는 예
#include <iostream>
using namespace std;
template <typename T>
T GetMax(T a, T b)
{
T result;
result = (a > b) ? a : b;
return (result);
}
int main()
{
int i = 5, j = 6, k;
float m = 10.3, n = 5.5, p;
k = GetMax<int>(i, j);
p = GetMax<float>(m, n);
cout << k << endl;
cout << p << endl;
return 0;
}
템플릿 파라미터(<typename T>)의 실제 자료형을 결정하는 위의 <int>, <float> 형식을 생략해도 아규먼트 자료형을 참조하여 자동으로 해당 자료형으로 구체화된 함수가 생성되고 호출된다
k = GetMax(i, j);
p = GetMax(m, n);
템플릿 파라미터가 다수개인 경우
template<typename T1, typename T2>
double Add(T1 a, T2 b);
템플릿에 const와 참조(&)형을 사용하는 예
template <typename T>
const T& Max(const T& a, const T& b )
{
return a > b ? a : b;
}
클래스 템플릿의 예
#include <iostream>
using namespace std;
template <typename T>
class mypair
{
T values[2];
public:
mypair(T first, T second)
{
values[0] = first; values[1] = second;
}
};
int main()
{
mypair<double> pair(1.2, 3.4);
return 0;
}
위의 예제에서 클래스 선언부 밖에 멤버함수를 정의하는 경우에는 각 멤버함수 정의부마다 클래스 템플릿 선언이 선행되어야 한다
template <typename T>
class mypair
{
T a, b;
public:
mypair (T first, T second)
{
a=first; b=second;
}
T getmax ();
};
// 선언부 밖에 함수 정의를 두는 경우
template <typename T>
T mypair<T>::getmax ()
{
T retval;
retval = a>b? a : b;
return retval;
}
템플릿 전문화(Template Specialization)
템플릿 파라미터에 특정 자료형이 지정되는 경우 함수 안에서 해당 자료형의 처리방법이 달라서 템플릿에서 처리할 수 없는 경우에는 특별한 처리방법을 적용해야 한다. 이런 경우에 템플릿 전문화 방법을 사용하여 함수 템플릿을 오버라이드하면 된다
함수 템플릿 전문화의 예
#include <iostream>
using namespace std;
// 일반 함수 템플릿
template<typename T>
T Add(T a, T b)
{
T result = a+b;
return result;
}
// 함수 템플릿 전문화(위의 함수 템플릿을 오버라이드 한 것)
// 템플릿 파라미터가 모두 double 자료형이라면 이 함수가 우선적으로 호출된다
template<>
double Add(double a, double b)
{
double result = a+b;
return result;
}
int main()
{
int sum = Add<int>(2, 3); // <int> 는 생략가능
cout << "정수 합계:" << sum << endl;
double dsum = Add<double>(2.1, 3.2); // <double> 은 생략가능
cout << "실수 합계:" << dsum << endl;
return 0;
}
클래스 템플릿 전문화 선언 형식
template <typename T> class mycontainer { ... }; // 일반 클래스 템플릿 선언
template <> class mycontainer <char> { ... }; // 클래스 템플릿 전문화(위의 클래스 템플릿을 오버라이드함)
클래스 템플릿 전문화 선언 및 사용 예
// template specialization
#include <iostream>
using namespace std;
// class template:
template <typename T>
class mycontainer {
T element;
public:
mycontainer (T arg) {element=arg;}
T increase () {return ++element;}
};
// 클래스 템플릿 전문화: 기본 템플릿 클래스의 모든 멤버를 빠짐없이 다시 정의해야 한다
template <>
class mycontainer <char> {
char element;
public:
mycontainer (char arg) {element=arg;}
char uppercase ()
{
if ((element>='a')&&(element<='z'))
element+='A'-'a';
return element;
}
};
int main () {
mycontainer<int> myint (7); // 템플릿 파라미터에 전달할 <int> 를 생략하면 안됨
mycontainer<char> mychar ('j'); // 클래스 템플릿 전문화 사용 <char>를 생략하면 안됨
cout << myint.increase() << endl;
cout << mychar.uppercase() << endl;
return 0;
}
Non-type 파라미터를 가진 템플릿
함수 템플릿이나 클래스 템플릿은 자료형을 일반화하여 선언하고 함수의 호출부나 클래스 객체 생성시에 실제 자료형을 지정하여 실제 함수나 클래스를 생성하는 수단으로 자주 사용된다. 그러나 자료형을 일반화하는 것이 아니라 자료형은 결정되어 있고 실제 값을 템플릿 파라미터로 지정하는 방법도 지원되며 이 경우 자료형(Type)을 지정하는 것이 아니라 실제 값을 지정하는 것이므로 Non-type 파라미터 템플릿이라고 한다
Non-type 파라미터 템플릿의 선언 및 사용 예
#include <iostream>
using namespace std;
template <typename T, int N> // Non-type 파라미터(int N) 선언
class mysequence {
T memblock [N]; // Non-type 파라미터 사용
public:
void setmember (int x, T value);
T getmember (int x);
};
template <typename T, int N>
void mysequence<T,N>::setmember (int x, T value) {
memblock[x]=value;
}
template <typename T, int N>
T mysequence<T,N>::getmember (int x) {
return memblock[x];
}
int main () {
mysequence <int,5> myints; // Non-type 파라미터에 실제 값 5를 지정함
mysequence <double,5> myfloats;
myints.setmember (0,100);
myfloats.setmember (3,3.1416);
cout << myints.getmember(0) << '\n';
cout << myfloats.getmember(3) << '\n';
return 0;
}
템플릿 파라미터에도 디폴트 값을 지정할 수 있다
template <typename T=char, int N=10>
class mysequence {..};
위에 선언된 클래스 템플릿을 이용하여 객체를 생성하는 경우
아래의 2가지 템플릿 사용법은 완전히 동일한 결과를 갖는다
mysequence<> myseq;
mysequence<char,10> myseq;
부분 템플릿 전문화의 예
template< typename T1, typename T2 >
class Test
{
public:
T1 Add( T1 a, T2 b )
{
cout << "일반 클래스 템플릿." << endl;
return a;
}
};
template< typename T1 >
class Test<T1,float>
{
public:
T1 Add( T1 a, float b )
{
cout << "부분 전문화 템플릿." << endl;
return a;
}
};
// 위에 선언된 템플릿 클래스 부분 전문화 사용 예
void main()
{
Test<int, int> test1;
test1.Add( 2, 3 );
Test<int, float> test2;
test2.Add( 2, 5.8f );
}
템플릿 파라미터에 포인터 형을 사용할 경우의 템플릿 전문화 예
template< typename T >
class TestP
{
public:
void Add()
{
cout << "일반 템플릿." << endl;
}
};
// T를 T*로 부분 전문화
template< typename T >
class TestP<T*>
{
public:
void Add()
{
cout << "포인터를 사용한 부분 전문화 템플릿." << endl;
}
};
#include <iostream>
using namespace std;
void main()
{
TestP<int> test1;
test1.Add();
TestP<int*> test2;
test2.Add();
}