C++

[C++] 템플릿(Template)(2)

홍성곤 2017. 12. 25. 22:27

1] 템플릿 개념의 확장
1. 클래스 템플릿의 확장

template <class T>
class Point
{
private:
    T xpos, ypos;
public:
    Point(T x=0, T y=0);
    void ShowPosition() const;
};

template <class T>
class BoundCheckArray
{
private:
    T *arr;
public:
    BoundCheckArray(T); 
};

위 클래스 기반으로 Point<int> 템플릿 클래스의 객체를 저장할 수 있는 객체는 어떻게 생성해야할까? 딱히 어렵지 않아 보인다.

BoundCheckArray<Point<int>> pointArr(50);

Point<Int>형 포인터라면, 다음과 같이 객체를 생성하면 된다.

BoundCheckArray<Point<int>*> pointArrPtr(50);

타입이 너무 길다면, typedef 선언으로 간략화 할 수 있다.

typedef Point<int>* POINT_PTR;

* 특정 템플릿 클래스의 객체를 인자로 받는 일반함수의 정의와 friend 선언
: Point<int>와 같은 템플릿 클래스의 자료형을 대상으로도 일반함수의 정의가 가능하고, 클래스 템플릿 내에서 이러한 함수를 대상으로 friend 선언도 가능하다. 


2] 클래스 템플릿의 특수화
: 클래스 템플릿의 특수화도 함수 템플릿의 특수화랑 매우 비슷하다.

1. 클래스 템플릿 특수화
: 클래스 템플릿을 특수화하는 이유는, 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는 다른 행동양식을 적용하기 위해서이다.

template <class T>
class SoSimple
{
public:
    T SimpleFunc(T num)
    {
        ...
    }
};

위 클래스의 int형 특수화는 다음과 같다.

template<>
class SoSimple<int>
{
public:
    int SimpleFunc(int num)
    {
        ...
    }
};

2. 클래스 템플릿의 부분 특수화

template <class T1, class T2>
class MySimple { ... };

다음은 위 클래스 템플릿을 부분 특수화한 코드이다.

template <class T1>
class MySimple<T1, int> { ... }

그렇다. 두개의 템플릿 타입에 대하여 하나의 타입만 특수화를 한것이다. 

template <>
class MySimple<double, int> { ... }

위는 전체 타입에 대해 특수화를 시킨것이다. 만약 두개의 정의가 겹치는 즉 MySimple<double, int> 타입의 객체를 생성하려고 하면 전체 특수화에 대한 클래스 템플릿이 우선시 된다. 


3] 템플릿 인자
: 위에서 사용된 T 또는 T1, T2 같은 문자를 가리켜 "템플릿 매개변수" 라고 한다. 그리고 템플릿 매개변수에 전달되는 자료형 정보를 가리켜 "템플릿 인자" 라고 한다. 

1. 템플릿 매개변수에는 변수의 선언이 올 수 있다. 
: 다음의 클래스 템플릿 정의를 보자. 이 정의에서 독특한 사실은 템플릿 매개변수의 선언에 마치 함수처럼 변수의 선언이 등장했다는 점이다.

template <typename T, int len> 
class SimpleArray
{
private:
    T arr[len];
public:
    T& operator[] (int idx)
    {
        return arr[idx];
    }
};

이렇듯 템플릿 매개변수에도 변수가 올 수 있다. 그리고 이를 기반으로 다음의 형태로 객체생성이 가능하다.

SimpleArray<int, 5> i5arr;
SimpleArray<double, 7> d7arr;

위의 두 문장에서 템플릿 매개변수 len에 전달된 인자 5와 7은 해당 템플릿 클래스에서 상수처럼 사용된다. 즉, len은 각각 5와 7로 치환되어 템플릿 클래스가 각각 생성된다.

class SimpleArray<int, 5>
{
private:
    int arr[5];
public:
    int& operator[] (int idx)
    {
        return arr[idx];
    }
};

class SimpleArray<double, 7>
{
private:
    int arr[7];
public:
    double& operator[] (int idx)
    {
        return arr[idx];
    }
};

물론, 위의 두 템플릿 클래스 SimpleArray<int, 5>와 SimpleArray<double, 7>은 완전히 다른 타입이다. 

위에서 보인 것처럼 배열의 길이를 결정하기 위해서 굳이 위에처럼 하지말고 생성자를 이용해서 하면 더욱 편리할 것이다. 
그런데 굳이 이러한 템플릿 클래스를 만드는 이유는, 길이가 다른 두 배열 객체간의 대입 및 복사에 대한 부분을 신경 쓰지 않아도 된다는 것이다. 서로 다른 타입이기 때문에 길이가 다른 배열에 객체에 대해 대입 및 복사 연산을 할 경우 컴파일 에러가 발생할 것이기 때문이다. 
만약 생성자를 이용해서 배열의 길이를 결정하게 했다면, 길이가 같은 배열에 대해서만 대입을 허용하기 위해서 추가적인 코드의 삽입이 불가피하며, 이러한 추가적인 코드는 대입 및 복사의 과정에서 CPU가 수행해야 할 일을 늘리는 결과로 이어진다. 

2. 템플릿 매개변수는 디폴트 값 지정도 가능하다.
: 템플릿 매개변수에도 디폴트 값의 지정이 가능하다. 

template <class T=int, int len=7> 
class SimpleArray
{
    ....
}

SimpleArray <> arr;


4] 템플릿과 static
1. 함수 템플릿과 static 지역변수

template <class T>
void ShowStaticVlaue(void)
{
    static T num = 0;
    num += 1;
    cout << num << endl;
}

위의 함수 템플릿은 다음과 같은 템플릿 함수를 만들어 내며 각각의 함수가 static 변수를 따로 가지고 있다. 

void ShowStaticVlaue<int>(void)
{
    static int num = 0;
    num += 1;
    cout << num << endl;
}

void ShowStaticVlaue<long>(void)
{
    static long num = 0;
    num += 1;
    cout << num << endl;
}

2. 클래스 템플릿과 static 멤버변수

template <class T>
class SimpleStaticMem
{
private:
    static T mem;
public:
    void  AddMem(int num)
    {
        mem += num;
    }

    void ShowMem()
    {
        cout<<mem<<endl;
    }
};

template <class T>
T SimpleStaticMem<T>::mem = 0; // 템플릿 기반의 static 멤버 초기화 문장이다.

위 클래스 템플릿도 타입 별 static 멤버 변수가 각각 유지된다.

3. 템플릿 static 멤버변수 초기화의 특수화
위에서 다음과 같이 static 멤버변수를 초기화 하였다.

template <class T>
T SimpleStaticMem<T>::mem = 0;

그러면 long 타입만 5로 초기화하고 싶을때는 어떻게 할까? 간단하다.

template <>
long SimpleStaticMem<long>::mem = 5;