스물여덟번째 열혈강의
점점 마지막에 다다르고 있다. 이제 템플릿에 대해 조금씩 더 정리해 보도록 하자.
함수 템플릿의 특수화 (Specialization)
먼저 예제와 실행결과를 한번 보도록 하자.
#include <iostream>
using namespace std;
template <typename T>
T Max(T a, T b){
return a > b ? a : b;
}
int main(){
cout << Max(11, 15) << endl;
cout << Max('T', 'Q') << endl;
cout << Max(3.5, 7.5) << endl;
cout << Max("Sample", "Best") << endl;
return 0;
}
15
T
7.5
Best
위 예제의 함수 템플릿 Max는 두 데이터 중 큰 값을 반환한다. 근데 문자열일 경우를 보면 아무 의미가 없다.(단순 주소값을 비교한 결과이다.) 만약 문자열의 길이 비교가 목적이라면 다음과 같은 코드로 바뀌어야 한다.
const char* Max(const char* a, const char* b){
return strlen(a) > strlen(b) ? a: b;
}
만약 사전 순서라면 다음과 같다.
const char* Max(const char* a, const char* b){
return strcmp(a, b) > 0 ? a : b;
}
이렇게 상황에 따라 예외를 둘 필요가 있는데 이 때 바로 함수 템플릿의 특수화 (Specialization) 가 사용된다.
#include <iostream>
using namespace std;
template <typename T>
T Max(T a, T b){
return a > b ? a : b;
}
template <>
char* Max(char* a, char* b){
cout << "char* Max(char* a, char* b)" << endl;
return strlen(a) > strlen(b) ? a: b;
}
template <>
const char* Max(const char * a, const char* b){
cout << "const char* Max(const char * a, const char* b)" << endl;
return strcmp(a, b) > 0 ? a : b;
}
int main(){
cout << Max(11, 15) << endl;
cout << Max('T', 'Q') << endl;
cout << Max(3.5, 7.5) << endl;
cout << Max("Sample", "Best") << endl;
char str1[] = "Simple";
char str2[] = "Best";
cout << Max(str1, str2) << endl;
return 0;
}
15
T
7.5
const char* Max(const char * a, const char* b)
Sample
char* Max(char* a, char* b)
Simple
위 코드를 한번 보자.
template <>
char* Max(char* a, char* b){
cout << "char* Max(char* a, char* b)" << endl;
return strlen(a) > strlen(b) ? a: b;
}
이 뜻은 다음과 같다. char* 형 함수는 이렇게 제시하니, char* 형 템플릿 함수가 필요한 경우 별도로 만들지말고 이것을 쓴다.
template <>
const char* Max(const char * a, const char* b){
cout << "const char* Max(const char * a, const char* b)" << endl;
return strcmp(a, b) > 0 ? a : b;
}
위 문장도 비슷하다.
const char* 형 함수는 이렇게 제시하니, const char* 형 템플릿 함수가 필요한 경우 별도로 만들지말고 이것을 쓴다.
그리고 이 두 함수 템플릿의 특수화
정의 형태는 특수화하는 자료형의 정보 <char>와 <const char>를 생략한 형태이고, 이를 생략하지 않는 다면 다음과 같다.
template <>
char* Max<char*>(char* a, char* b){
cout << "char* Max(char* a, char* b)" << endl;
return strlen(a) > strlen(b) ? a: b;
}
template <>
const char* Max<const char*>(const char * a, const char* b){
cout << "const char* Max(const char * a, const char* b)" << endl;
return strcmp(a, b) > 0 ? a : b;
}
물론, 위와같이 가급적 명시하는 것이 명확하게 하는 방법이 될 수 있다.
클래스 템플릿
앞서, 함수를 템플릿으로 정의 했듯, 클래스 역시 가능하다. 그리고 이를 클래스 템플릿 이라고 하고, 이를 기반으로 컴파일러가 만들어 내는 클래스를 템플릿 클래스 라 한다.
앞서 다음과 같은 클래스를 정의 한적 있다.
class BoundCheckIntArray
class BoundCheckPointArray
class BoundCheckPointPtrArray
이는 모두 배열 클래스 인데, 저장의 대상이 달라 따로 만들었다. 즉, 제공되는 기능이나 행동이 같은데, 저장의 대상이 다르다는 이유로 클래스를 3개나 정의한다면 매우 비효율적이다. 이럴때 클래스 템플릿
이 이를 해소해준다.
그럼 정의 방법과 객체 생성방법을 알아보자.
class Point{
private:
int xpos, ypos;
public:
Point(int x=0, int y=0) : xpos(x), ypos(y){}
void ShowPosition() const{
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
};
이 클래스는 좌표정보를 정수로 표현한다. 그러므로 문자나 실수의 형태를 하고 싶다면, 템플릿화가 더 좋을 것이다.
template <typename T>
class Point{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0) : xpos(x), ypos(y){}
void ShowPosition() const{
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
}
아마 직관적으로 함수 템플릿과 동일함을 느낄 것이다. 그럼 한번 예제를 보자.
#include <iostream>
using namespace std;
template <typename T>
class Point{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0) : xpos(x), ypos(y){}
void ShowPosition() const{
cout << '[' << xpos << ", " << ypos << ']' << endl;
}
};
int main(){
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;
}
여기서 함수 템플릿과는 다른 점이 하나 있다. 함수에서는 자료형을 생략 가능 했지만, 클래스 템플릿에서는 불가능 하다.
클래스 템플릿 선언과 분리
클래스 템플릿도 멤버함수를 클래스 외부에 정의하는 것이 가능하다. 다음을 보자.
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 클래스 템플릿
예제를 만들어 보자.
#include <iostream>
using namespace std;
template <typename T>
class Point{
private:
T xpos, ypos;
public:
Point(T x=0, T y=0);
void ShowPosition() const;
};
template <typename T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y){}
template <typename T>
void Point<T>::ShowPosition() const{
cout << '[' << xpos << ", " << ypos << ']' << endl;
};
int main(){
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> pos1(3, 4);
Point<double> pos2(2.4, 3.6);
Point<char> pos3('P', 'F');
위와 같이 main에 선언되어 있다면, Point 템플릿이 어떻게 구성되어 있는지 모른다. 물론 같이 컴파일을 하지만 각각 다른 소스파일이므로 참조하지 않아 모를 수 밖에 없다. 따라서 위 파일에 해당하는 템플릿 header와 cpp 파일을 include하여야 한다.
다음은 템플릿을 조금더 확장하여 알아보도록 하자.