템플릿 (Template) 프로그래밍
카테고리: Cpp
🤯 언리얼을 하기 위해 C++ 기억 되살리기 프로젝트
템플릿 (Template) 프로그래밍
- 템플릿이란?
- java와 c#에서의 제네릭(generic) 메서드/클래스와 비슷 (T)
- STL 컨테이너 또한 템플릿
- 덕분에 코드를 자료형마다 중복으로 작성하지 않아도 됨
1. 함수 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
// Main.cpp
#include <vector>
int main()
{
std::vector<int> scores; // vector<int> 요게 바로 템플릿 프로그래밍
// 컴파일시에 컴파일러가 자동으로 코드를 만들어주는 것
scores.push_back(10);
scores.push_back(50);
return 0;
}
1-1. 두 정수 더하기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// MyMath.h ------------------------
template <typename T> // 또는 template <class T>
T Add(T a, T b)
{
return a + b;
}
// Main.cpp ------------------------
#include "MyMath.h"
int main()
{
std::cout << Add<int>(3, 10) << std::endl;
std::cout << Add<float>(3.14f, 10.14f) << std::endl;
return 0;
}
- 함수 템플릿을 호출할 땐 템플릿 매개변수를 생략할 수 있음
1 2
Add<int>(3, 10); Add(3, 10); // int라고 추측 가능해서 함수 돈다
1-2. typename vs class
template <typename T>
or template <class T>
- 실질적인 차이는 없음
- 그냥 typename을 쓰자
1-3. 템플릿은 어떻게 작동할까?
- 템플릿을 인스턴스화 할 때 마다 컴파일러가 내부적으로 코드 생성
- T에 매개변수를 넣어서 쓰기 시작할 때 마다.
- 템플릿에 넣는 자료형 가짓수에 비례해서 exe 파일 크기 증가
- 컴파일 도중에 다형성을 부여할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// MyMath.h ------------------------
template <typename T>
T Add(T a, T b)
{
return a + b;
}
// Main.cpp ------------------------
#include "MyMath.h"
int Add(int a, int b)
{
// 예시 함수
// 컴파일 도중에 컴파일러가 코드를 생성한다는 것을 보여주는 예시
// 그래서 반환형이 int
return a + b;
}
float Add(float a, float b)
{
// 예시 함수
// 컴파일 도중에 컴파일러가 코드를 생성한다는 것을 보여주는 예시
// 그래서 반환형이 float
return a + b;
}
int main()
{
int resultInt = Add<int>(3, 10); // 템플릿 인스턴스를 만들어서, 컴파일러가 코드를 생성하게 됨
float resultFloat = Add<float>(3.14f, 10.14f);
}
2. 클래스 템플릿
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// MyArray.h ------------------------
#pragma once
template<typename T> // 클래스 템플릿이야~ T에 따라 반환형 바뀌어~
class MyArray
{
public:
bool Add(T data); // T data를 넣을거다
MyArray();
private:
enum { MAX = 3 };
int mSize;
T mArray[MAX]; // T형의 데이터 3개만큼 배열 만들자
};
// MyArray.cpp ----------------------
#include "MyArray.h"
template<typename T>
bool MyArray<T>::Add(T data) // 템플릿 클래스 함수 작성할 때 cpp에 :: 이렇게 작성해야 한다는 거 명심!
{
if (mSize >= MAX)
{
return false;
}
mArray[mSize++] = data;
return true;
}
template<typename T>
MyArray<T>::MyArray()
: mSize(0) // 생성자. 템플릿화 된 MyArray 클래스.
{
}
// Main.cpp -------------------------
#include "MyArray.h"
int main()
{
MyArray<int> scores;
scores.Add(10);
scores.Add(20);
scores.Add(30); // 여기까지 다 TRUE 반환이지만
scores.Add(40); // FALSE 반환 (size 초과)
return 0;
}
근데 위의 코드를 컴파일 하면 에러가 난다…! ‘찾을 수 없는 외부 심볼’ 어쩌고 하면서.
- 컴파일러가
Main.cpp
를 컴파일할 때,MyArray.cpp
를 못 찾음.MyArray.h
를 통해서 오직MyArray
클래스 선언만 볼 수 있음;- 구현체는
MyArray.cpp
에 있으므로 찾질 못함 - 헤더와 cpp는 따로 컴파일 되기 때문에, 컴파일 중엔 찾을 수 없어요
- 그래서 컴파일러가 int형으로 구현되는 MyArray
를 찾을 수 없음
- 따라서 컴파일러가
MyArray<int>
를 만들어 줄 수 없음…
💡 클래스 템플릿 구현 시, 헤더 부분에 구현체를 옮겨야 한다!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// MyArray.h
template<typename T>
class MyArray
{
public:
bool Add(T data);
MyArray();
private:
enum { MAX = 3 };
int mSize;
T mArray[MAX];
};
template<typename T>
inline bool MyArray<T>::Add(T data)
{
return false;
}
template<typename T>
inline MyArray<T>::MyArray()
: mSize(0)
{
}
이렇게 헤더 부분에 구현체를 적어두면,
컴파일러가 확인하고 바꿔준다.
다만 이렇게 할 경우 크기도 늘어나고, 컴파일 속도고 느려진다.
헤더는 여기저기 쓰이기 때문…
2-1. 개체를 선언할 때 템플릿 매개변수를 명시해야 함
1
2
MyArray<int> scores; // 에러 안남
MyArray scores; // 에러
컴파일러가 알 수 없기 때문에 에러가 난다.
함수는 호출할 때 매개변수에서 추측할 수 있는데,
클래스는 아니기 때문.
3. 템플릿 배열
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// MyArray.h ----------------------
template<typename T>
class MyArray
{
public:
MyArray();
bool Add(const T& data); // T 는 어떤 타입도 될 수 있음.
// 그러다보니 엄청 큰, 10mb 정도의 오브젝트 타입도 될 수 있음
// 그거 다 복사하기 쫌 그러므로, 참조로 받음!
// 매개변수로 전달할 때 만큼은 참조로 받겠다~
size_t GetSize() const;
private:
enum { MAX = 3 };
size_t mSize;
T mArray[MAX];
};
template<typename T>
MyArray<T>::MyArray() // 템플릿화 된 클래스니까, 이렇게 MyArray<T>:: 붙여줘야 함!
: mSize(0)
{
}
template<typename T>
size_T MyArray<T>::GetSize() const
{
return mSize;
}
template<typename T>
bool MyArray<T>::Add(const T& data)
{
if (mSize >= MAX)
{
return false;
}
mArray[mSize++] = data;
return true;
}
// MyArraySample.cpp ----------------------
void MyArrayExample()
{
MyArray<int> scores;
scores.Add(10);
scores.Add(50);
std::cout << "scores - Size: " << scores.GetSize() << std::endl;
MyArray<IntVector> intVectors; // 클래스 템플릿은 오브젝트 타입도 될 수 있다! IntVector
intVectors.Add(IntVector(1, 1));
intVectors.Add(IntVector(5, 3));
std::cout << "intVectors - Size: " << intVectors.GetSize() << std::endl;
MyArray<IntVector*> intVectors2; // 포인터 담을 때 예시
IntVector* intVector = new IntVector(3, 2);
intVectors2.Add(intVector);
std::cout << "intVectors2 - Size: " << intVectors2.GetSize() << std::endl;
delete intVector;
}
4. 클래스 템플릿 트릭
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// FixedVector.h
// 벡터 쓰고 싶은데, 사이즈가 늘어나지 않았으면 좋겠다!
// 그래서 나만의 벡터를 만든 것. 타입마다 만들지 않기 위해 템플릿화 했다.
template<typename T, size_t N>
class FixedVector
{
public:
// public 메서드들
private:
T mArray[N];
};
// main.cpp
FixedVector<int, 16> numbers;
5. 두 개의 템플릿 매개변수
1
2
3
4
5
6
7
8
9
10
11
12
13
template<typename T, typename U>
void Print(const T& a, const U& b)
{
std::cout << a << " / " << b << std::endl;
}
template<typename T, typename U>
class MyClass
{
private:
T mX;
U mY;
}
6. 템플릿 특수화 (Specialization)
템플릿(c#에선 제네릭)은 즉 일반화 같은 것이다.
똑같이 만들어야 되는 여러 클래스를 범용적으로 뭉쳐서 하나로 만드는 것이다.
특수화란 특별한 경우를 위한 특별한 하나를 만드는 것이다.
쓰일 일은 잘 없지만…
- 특정한 템플릿 매개변수를 받도록 템플릿 코드를 커스터마이즈
1 2 3 4 5
template <class T, class Allocator> class std::vector<T, Allocator> {} // 모든 형을 받는 제네릭 vector template <class Allocator> class std::vector<bool, Allocator> // bool을 받도록 특수화된 vector
- 전체 템플릿 특수화
- 템플릿 매개변수 리스트가 비어 있음
1 2 3 4 5
template<typename VAL, typename EXP> VAL Power(const VAL value, EXP exponent) {} // 모든 형을 받는 제네릭 power template<> // 비어 있음! 모두 내가 특정한 타입만을 받도록 하겠다. float Power(float value, float exp) // float형을 받도록 특수화
- 부분 템플릿 특수화
1 2 3 4 5
template <class T, class Allocator> class std::vector<T, Allocator> {} // 모든 형을 받는 제네릭 tempalte <class Allocator> class std::vector<bool, Allocator> // bool 을 받도록 특수화
댓글남기기