[C++ : 템플릿 기초 ]
C++에서 가장 어려운 부분 중 하나인 템플릿.
기초를 다뤄보자.
Template(템플릿) : 함수나 클래스를 찍어내는 틀
[ 함수 템플릿 ]
void Print(int x)
{
cout << x << "\n";
}
void Print(float x)
{
cout << x << "\n";
}
void Print(double x)
{
cout << x << "\n";
}
void Print(const char* x)
{
cout << x << "\n";
}
구현부가 같은데, 인자들만 편리하게 바꿀 수 없을까?
템플릿을 사용하면 된다.
template<typename T>
void Print(T a)
{
cout << a << "\n";
}
인자로 int, float, const char* 등을 넣어주면 실행이 잘 된다.
동일한 함수를 다르게 호출하는 개념이 아니라 "틀"을 만들어준 것이고, 하나라도 사용하는 순간 컴파일러가 직접적으로 각기 버전을 만들어 낸다. (int, float 버전 등등)
컴파일러가 형을 추론해서 만들어주는 것이다.
프로그래머가 타입을 지정해서 만들수도 있는데, <>를 사용
Print<int>(50);
지정된 int 타입으로만 실행되는 것.
template<typename T>
template<class T>
둘 다 사용가능.
함수의 인자와, 리턴 타입도 T를 사용할 수 있다.
template<typename T>
T Add(T a, T b)
{
return a + b;
}
/* ... */
float ret = Add<float>(1.1f, 2.2f);
인자를 int, const char* 이렇게 두 종류로 사용하는 것도 가능하다.
template<typename T1, typename T2>
void Print(T1 a, T2 b)
{
return a + b;
}
[ 함수 템플릿 특수화 ]
template<typename T>
void Print(T a)
{
cout << a << "\n";
}
ostream& operator<<(ostream& os, const Knight& k)
{
os << k._hp;
return os;
}
/* ... */
Knight k1;
Print(k1);
Print()에 Knight 객체를 넣어서 cout을 실행시킨다면, 처음에는 오버로딩되지 않아 에러를 뱉지만, 위 처럼 연산자 오버로딩을 전역으로 추가함으로써 통과가 될 것이다.
- 템플릿 특수화
template<typename T>를 통해 모든 타입에 적용되는 규칙을 만들어 줬지만, 예외적으로 특정 타입에만 다른 규칙을 따르게 하는 것.
// 템플릿 특수화
template<>
void Print(Knight a)
{
cout << "Print Knight" << "\n";
cout << a._hp << "\n";
}
예외적으로 Knight 타입이 들어온다면 위 버전을 따르게 됨.
T가 아니라 특정 타입에만 해당되는 것이니 template<>을 쓰는 것.
어셈블리 코드를 보면, call 하는 함수 주소가 타입마다 다 다르다는 것을 알 수 있다.
→ 함수가 각자 여러 개 만들어 졌다는 것.
[ 클래스 템플릿 ]
위 같은 상황은 클래스에서도 동일하다.
클래스의 멤버 변수가 int 였다면, 이를 float 타입, double 타입 등 여러 타입에 대해서도 유연하게 만들어지기를 원할 것이다.
예를 들면, 자주 쓰는 stl 동적 배열인 vector만 봐도, vector<int> v; 이렇게 쓴다.
template<typename T>
class RandomBox
{
public:
T GetRandomData()
{
int idx = rand() % 10;
return _data[idx];
}
public:
T _data[10];
}
/* ... */
RandomBox<int> rb1;
RandomBox<float> rb2;
그런데, 템플릿안에 무조건 typename을 붙여야 하는 것은 아니다.
template<typeanme T, int SIZE>
class RandomBox
{
public:
T GetRandomData()
{
int idx = rand() % SIZE;
return _data[idx];
}
public:
T _data[SIZE];
}
/* ... */
RandomBox<int, 10> rb1;
RandomBox<int, 20> rb2;
RandomBox<int, 10>과 RandomBox<int, 20>은 서로 별개의 클래스 취급이 된다.
rb1 = rb2;는 에러를 뱉어냄.
복사할 때, 템플릿 인자를 받아준다면 반드시 두 개의 타입이 100% 일치할 때만 통과가 된다.
[ 클래스 템플릿 특수화 ]
특정 타입만 예외를 만들겠다.
// 클래스 템플릿 특수화
template<int SIZE>
class RandomBox<double, SIZE> // 특수한 버전이라는 것을 알려주기 위해 <double, SIZE> 표시해줘야함.
{
public:
double GetRandomData()
{
cout << "RandomBox Double" << "\n";
int idx = rand() % SIZE;
return _data[idx];
}
public:
double _data[SIZE];
};
[ 정리 ]
template<typename T, int SIZE>
typename T는 어떤 타입도 가능하게 해주는 조커카드. SIZE는 아무거나 어떤 정수를 골라주는 두번째 요구사항.
이럴경우 T 타입은 같아도, SIZE만 달라져도 완전히 서로 다른 클래스로 인식함/
[ Reference ]
Rookiss, Part1 C++ 입문
'컴퓨터공학 > C++' 카테고리의 다른 글
[C++] std::move() (0) | 2024.11.09 |
---|---|
[C++] 전방 선언 (0) | 2024.11.05 |
[C++] 타입 변환과 얕은 복사, 깊은 복사 (0) | 2024.11.05 |
[C++] Virtual function performance (1) | 2024.11.03 |
[More Effective C++] 24 : 가상함수, 다중상속, 가상 기본 클래스, RTTI에 들어가는 비용을 제대로 파악하자 (0) | 2024.11.03 |