Publish:

태그: , ,

카테고리:


🤯 언리얼을 하기 위해 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 을 받도록 특수화
    

이슈 및 공부한 것을 기록해두는 개인 블로그 입니다. 댓글, 피드백 환영합니다 🙂

Update:

댓글남기기