본문으로 바로가기

STL) C++ 템플릿

category C++/STL 2019. 2. 18. 04:16

<개요>


이번엔 STL의 기초가 되는 템플릿에 대해 작성합니다.

템플릿은 일반화 프로그래밍 (Generic Programming) 이라는 C++ 프로그래밍의 기준입니다.

많은 데이터 타입에 대해 동일한 작업이 필요할 때, 주로 사용되며 

타입과 알고리즘에 상관없이 항상 동일하게 작동합니다. 


변수로도 템플릿을 작성할 순 있지만, 따로 작성하진 않겠습니다.


<함수형 템플릿>


예를 들어 두 정수 값을 받아 max를 출력해주는 함수를 하나 만들겠습니다.


1
2
3
4
5
6
7
8
int max(int lhs, int rhs) {
    return (lhs > rhs ? lhs : rhs);
}
 
int main() {
    int a = max(1020);    // 20
    int b = max(2030);    // 30
}
cs

 

자 이제 int 와 double 을 비교해주는 함수가 하나 필요해졌습니다.

앗 double 과 double 을 비교해주는 함수도 필요해졌습니다.


1
2
3
4
5
6
7
8
9
int max(int lhs, int rhs) {
    return (lhs > rhs ? lhs : rhs);
}
decltype(auto) max(int lhs, double rhs) {
    return (lhs > rhs ? lhs : rhs);
}
double max(double lhs, double rhs) {
    return (lhs > rhs ? lhs : rhs);
}
cs


필요해지면 더 추가해야할까요? string 형과 int , string과 string, char 와 string 등등 비교할 타입이 많아질 때, max는 계속 오버로딩을 해줘야 할것입니다.

이럴때 template 을 사용하면 유용합니다.
template 은 다양한 타입 내에서 동일한 알고리즘을 이용해야 할때 주로 사용됩니다.

1
2
3
4
5
6
7
8
9
template <typename T, typename U>
auto max(T lhs, U rhs) {
    return lhs > rhs ? lhs : rhs;
}
 
int main() {
    auto a = max(1025.3); // 25.3
    auto b = max(1020);    // 20
}
cs

a 를 보시면, 10은 int 25.3은 double입니다. 저 값들은 max로 들어가

1
2
3
4
template <typename inttypename double>
auto max(int lhs, double rhs) {
    return lhs > rhs ? lhs : rhs;
}
cs

이렇게 치환됩니다. 인자의 타입 순서에따라 맞게 들어간다고 보시면 됩니다. 
T 와 U 는 & 또는 *로도 받을 수 있기 때문에, 큰 용량에 대해선 꼭 참조로 받으시는게 좋습니다.

1
2
3
4
5
template <typename T, typename U>
decltype(auto) max(const T& lhs, const U& rhs) {    
    return lhs > rhs ? lhs : rhs;    // const &로 받은건 rvalue도 올 수 있음
}
 
cs

각 타입의 자료형을 달리하는게 아닌, 하나의 자료형으로 사용하거나 기본 자료형이 아닐 경우 템플릿을 구체화 시켜주어야 합니다.

1
2
3
4
5
6
7
8
9
template <typename T>
T max(T t1, T t2) {
    return t1 > t2 ? t1 : t2;
}
 
int main() {
    int a = max<int>(1025.3); // 두 인수를 다 int 형으로 변환함
    double b = max<double>(1025.3);    // 두 인수를 다 double 형으로 변환함
}
cs

위와 같이 <int><double> 이 안붙을 경우를 암시적 구체화, 붙을 경우 명시적 구체화라고 합니다.

<클래스형 템플릿>

위와 같이 클래스 또한 template 으로 작성할 수 있습니다.

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
class A {
public:
    A(const T& rhs) : m_t(rhs) { }
private:
    T m_t;
};
int main() {
    A aa(10);    // error, 무엇으로 class 를 만들어야 할 지 몰라요
    A<int> bb(10);    // ok, T가 int 인 A class를 만듭니다.
}
cs

클래스 내의 함수에 대해서도 템플릿 작성이 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template <typename T>
class A {
public:
    A(const T& rhs) : m_t(rhs) { }
    template <typename U>
    void printsum(const U& rhs) {
        cout << m_t + rhs << endl;
    }
private:
    T m_t;
};
int main() {
    A<int> bb(10);
    bb.printsum(5.2);    //15.2
    bb.printsum<double>(5.2);    //15.2
}
cs



<공통>

모든 템플릿은 부분 특수화와 완전 특수화가 가능합니다.
예를 들어, 바이트를 구하는 함수를 하나 정의해 보겠습니다.

1
2
3
4
5
6
7
8
9
10
template <typename T>
int mysize(const T rhs) {
    return sizeof(rhs);
}
int main() {
    int i = 0;
    double j = 0.0;
    cout << mysize(i) << endl;    // 4byte
    cout << mysize(j) << endl;    // 8byte
}
cs

그런데 char 형과 string 형에 대해선, 바이트가 아닌 길이를 알려주고 싶어졌습니다. 재 작성해야할까요?
답은 특수화에 있습니다. 우리가 일반 함수에서 인자타입에 따라 오버로딩 해줬듯이, 템플릿의 특수한 인자타입에만 맞는 템플릿을 재정의 해주는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename T>
int mysize(const T rhs) {
    return sizeof(rhs);
}
template <> 
int mysize<const char*>(const char* c) {
    return strlen(c);
}
template <>
int mysize(const char* rhs) {
    return strlen(rhs);
}    // 위 함수와 동일합니다.
int main() {
    int i = 0;
    double j = 0.0;
    cout << mysize(i) << endl;    // 4byte
    cout << mysize(j) << endl;    // 8byte
    cout << mysize("abcdedfg"<< endl;    // 8
}
cs

위를 템플릿의 완전 특수화 라고 합니다. ( 함수 템플릿은 부분 특수화가 없습니다! )

그럼 클래스로 넘어가 보겠습니다.

1
2
3
4
5
6
7
8
9
10
template <typename T, typename U>
class A;
 
template<> class A <intdouble>;
// 클래스 A에 대한 완전 특수화
template <typename T, typename U>
class A<T, U*>// 클래스 A에 대한 부분 특수화
 
template <typename T, typename U>
class A<T*, U*>;    // 클래스 A에 대한 부분 특수화
cs



class 는 부분 특수화와 완전 특수화의 개념이 있습니다.
만약 임의의 자료형을 가지고 작성됐다면, 그 템플릿 클래스는 부분 특수화 입니다.
하지만 명확한 자료형으로 작성됐다면, 완전 특수화입니다. 하지만
임의의 자료형과 명확한 자료형이 같이 사용된다면 부분 특수화 입니다.

<매개변수>

Microsoft Docs는 파라미터에 대한 정보를 작성했습니다.

1
2
3
4
5
6
7
8
9
10
11
template<typename T, size_t L>
class MyArray
{
    T arr[L];
public:
    MyArray() { ... }
};
 
int main() {
    MyArray<MyClass, 10> arr;
}
cs


위는 비형식 매개변수로, 임의의 자료형이 아닌 명확한 자료형도 올 수 있습니다.

1
2
3
4
5
6
7
template<typename T, template<typename U, int I> class Arr>
class MyClass2
{
    T t; // OK, T는 있습니다.
    Arr<T, 10> a;
    U u; //Error, U는 스코프 내에 없어요.
};
cs

U는 그저 Arr의 인자값으로 사용된 것이기 때문에 MyClass의 스코프 내에선 보이지 않습니다.
이렇게 템플릿 매개변수로 템플릿이 올 수 있습니다.

그리고 템플릿은 기본 템플릿 매개변수를 지정 할 수 있습니다.
예를 들어 vector를 보면, 기본 템플릿 매개변수로 std::allocator가 지정돼있습니다.

1
template <class T, class Allocator = allocator<T>> class vector;
cs

이는 벡터를 인스턴스화 시킬때

1
vector<int> myInts;
cs

이렇게 작성할 수도 있지만

1
vector<int, MyAllocator> ints;
cs

이렇게 작성할 수도 있습니다.

1
2
3
4
5
6
7
8
9
10
template<typename A = inttypename B = double>
class Bar
{
    //...
};
...
int main()
{
    Bar<> bar;
}
cs

템플릿의 모든 매개변수가 기본 매개변수라면, 빈꺽쇠로 초기화 할 수 있습니다.

정보 :
https://docs.microsoft.com/ko-kr/cpp/cpp/templates-cpp?view=vs-2017 ( Microsoft Docs 템플릿 )
https://docs.microsoft.com/ko-kr/cpp/cpp/template-specialization-cpp?view=vs-2017 ( Microsoft Docs 템플릿 특수화 )
http://tcpschool.com/cpp/cpp_template_class ( TCP School 템플릿 )
https://wikidocs.net/652 ( 함수 템플릿을 특수화 하지 말아야 하는 이유 )