템플릿
매개변수의 타입에 따라 함수나 클래스를 생성하는 매커니즘
- 여러 타입에서 동작할 수 있는 코드를 찍어내는 틀이라고 생각하면 된다.
- 템플릿을 사용하면 다양한 타입에 대해서 하나의 함수만 정의하면 되므로 전체 코드를 매우 간결하게 만들 수 있다.
- 템플릿을 통해 타입을 마치 인자인 것 마냥 사용하는 것을 일반화 프로그래밍(generic programming)이라고 한다.
템플릿 정의
- 템플릿은 함수 템플릿(Function Template)과 클래스 템플릿(Class Template)으로 나뉜다.
함수 템플릿 정의
template <typename 타입이름>
함수 원형
{
// 함수 본체
}
- typename 키워드를 template의 인자로서 받는다.
- 타입이름은 함수 원형과 본체에서 임의의 타입으로 사용되며, 함수 호출 시 전달된 매개변수 형에 맞춰 바뀐다.
- typename 키워드 대신 class 키워드를 넣어도 같은 동작을 하지만, 대부분 typename 키워드를 이용한다.
클래스 템플릿 정의
template <typename 타입이름>
class 클래스템플릿이름
{
// 클래스 멤버의 선언
}
- 타입이름은 클래스에서 임의의 타입으로서 사용되며, 인스턴스 생성 시 전달된 매개변수 형에 맞춰 바뀐다.
함수 템플릿 사용하기
swap 함수 예제
#include <iostream>
using namespace std;
// 함수 템플릿 프로토타입
template <typename T>
void Swap(T& a, T& b);
int main() {
int a = 10, b = 50;
cout << "swap 이전 >> " << "a = " << a << ", b = " << b << endl;
Swap(a, b);
cout << "swap 이후 >> " << "a = " << a << ", b = " << b << endl;
string c = "hello", d = "world";
cout << "swap 이전 >> " << "c = " << c << ", d = " << d << endl;
Swap(c, d);
cout << "swap 이후 >> " << "c = " << c << ", d = " << d << endl;
return 0;
}
// 함수 템플릿 정의
template <typename T>
void Swap(T& a, T& b) {
T temp;
temp = a;
a = b;
b = temp;
}
swap 이전 >> a = 10, b = 50
swap 이후 >> a = 50, b = 10
swap 이전 >> c = hello, d = world
swap 이후 >> c = world, d = hello
- T라는 타입이름을 선언하여 매개변수의 값을 바꾸는 Swap 함수를 구현하였다.
- 따로 함수에 매개변수 타입을 정의하지 않아도 T가 int형, string형 인자를 받음과 동시에 해당 타입으로 바뀌어 함수가 동작하였다.
- 이렇게 함수를 함수 템플릿으로 정의하면 int형, string형 함수를 따로 구현할 필요없이 하나의 함수만 구현하면 되므로 매우 효율적으로 프로그램을 작성할 수 있다.
함수 템플릿의 명시적 특수화 (Explicit Specialization)
- 같은 이름의 함수 템플릿 동작을 특정 타입에 대해서 다르게 설정할 수 있다.
#include <iostream>
using namespace std;
// 함수 템플릿 프로토타입
template <typename T>
void Swap(T& a, T& b);
// string 형에 대한 함수 템플릿 프로토타입
template <>
void Swap(string& a, string& b); // string형 인자를 받음을 명시
int main() {
int a = 10, b = 50;
cout << "swap 이전 >> " << "a = " << a << ", b = " << b << endl;
Swap(a, b);
cout << "swap 이후 >> " << "a = " << a << ", b = " << b << endl;
string c = "hello", d = "world";
cout << "swap 이전 >> " << "c = " << c << ", d = " << d << endl;
Swap(c, d);
cout << "swap 이후 >> " << "c = " << c << ", d = " << d << endl;
return 0;
}
// 함수 템플릿 정의
template <typename T>
void Swap(T& a, T& b) {
T temp;
temp = a;
a = b;
b = temp;
}
// string형에 대한 함수 템플릿 정의
template <> // 동작을 하지 않을 것이므로 typename을 지정하지 않았음
void Swap(string& a, string& b) {
// 아무것도 하지 않는다.
}
swap 이전 >> a = 10, b = 50
swap 이후 >> a = 50, b = 10
swap 이전 >> c = hello, d = world
swap 이후 >> c = hello, d = world
클래스 템플릿 사용하기
- 함수 템플릿을 호출할 때와 달리 클래스 템플릿을 사용할 때는 반드시 타입을 명시해야 한다. ▶이는 클래스의 생성자가 호출되기 이전에 멤버 변수의 타입을 확인하여, 객체의 메모리 공간을 할당해야 할 필요가 있기 때문이다.
- 타입을 인자로 전달하게 되면, 이후 컴파일러가 이를 확인하고 실제 코드를 생성한다. ▶ 이를 클래스 템플릿 인스턴스화(class template instantiation)라고 한다.
클래스 템플릿 특징
- 하나 이상의 인자를 가진다. ▶ template <typename T, int i> 는 2개의 템플릿 인자를 가짐.
- 디폴트 템플릿 인자를 명시할 수 있다. ▶ template <typename T=int> 는 디폴트 템플릿 인자가 int
- 기초 클래스로 상속할 수 있다. 아래는 예시.
// 기초 클래스
template <typename T>
class XClass {
};
// 상속
template <typename Type>
class YClass : public XClass<Type> {
//
};
숫자를 2배 곱하는 클래스 예제
#include <iostream>
using namespace std;
template <typename T>
class MyClass {
private:
T x;
public:
MyClass(T _x);
T Caldouble();
};
int main(void) {
MyClass<int> int_(10); // 인스턴스화 할 때 반드시 타입을 명시해야 함
MyClass<double> double_(1.2345);
cout << "int_ * 2 = " << int_.Caldouble() << endl;
cout << "double_ * 2 = " << double_.Caldouble() << endl;
return 0;
}
template <typename T>
MyClass<T>::MyClass(T _x) {
x = _x;
}
template <typename T>
T MyClass<T>::Caldouble() {
return x * 2;
}
int_ * 2 = 20
double_ * 2 = 2.469
- 함수 템플릿을 호출할 때와 달리 MyClass<type> 처럼 타입을 반드시 명시하여 전달해야 함에 유의한다.
- 타입을 명시하지 않을 시 다음과 같은 오류들이 발생한다.
클래스 템플릿 명시적 특수화
#include <iostream>
using namespace std;
// 클래스 템플릿
template <typename T>
class MyClass {
private:
T x;
public:
MyClass(T _x);
T Caldouble();
};
// double 형에 대한 명시적 특수화
template <>
class MyClass<double> {
private:
double x;
public:
MyClass(double _x) { x = _x; }
double Caldouble() {
cout << "double 형 데이터를 2배하여 출력합니다. " << endl;
return x * 2;
};
};
int main(void) {
MyClass<int> int_(10);
MyClass<float> float_(5.12f);
MyClass<double> double_(1.2345);
cout << "int_ * 2 = " << int_.Caldouble() << endl << endl;
cout << "float_ * 2 = " << float_.Caldouble() << endl << endl;
cout << "double_ * 2 = " << double_.Caldouble() << endl << endl;
return 0;
}
template <typename T>
MyClass<T>::MyClass(T _x) {
x = _x;
}
template <typename T>
T MyClass<T>::Caldouble() {
cout << "데이터를 2배하여 출력합니다. (double형 제외)" << endl;
return x * 2;
}
데이터를 2배하여 출력합니다. (double형 제외)
int_ * 2 = 20
데이터를 2배하여 출력합니다. (double형 제외)
float_ * 2 = 10.24
double 형 데이터를 2배하여 출력합니다.
double_ * 2 = 2.469
- double 형에 대한 클래스의 명시적 특수화를 구현하였다.
- double 형을 제외한 데이터 형들은 첫 번째의 클래스로 인스턴스를 생성하며, double 형은 두 번째 클래스로 인스턴스를 생성한다. ▶ 컴파일러는 전달된 인수에 대응하는 정의를 먼저 찾고, 발견 시 해당 정의를 사용한다.
- 다음과 같이 여러 인자가 있을 때 부분적으로 특수화를 구현할 수 있다.
// 3개의 인자를 가지는 클래스 템플릿 예
// 일반적인 클래스 템플릿
template <typename A, typename B, typename C>
class MyClass {};
// 첫 번째 인자에 대한 특수화 - A가 int일 경우
template <typename B, typename C>
class MyClass<int> {};
// 첫 번째, 세 번째 인자에 대한 특수화 - A가 int, C가 double일 경우
template <typename B>
class MyClass<int, B, double> {};
// 모든 인자에 대한 특수화 - A가 int, B가 int, C가 double일 경우
template <>
class MyClass<int, int, double> {};