본문 바로가기

프로그래밍 언어들/C++

CHAPTER14 - 템플릿(Template) 2

1. CHAPTER13에서 공부한 내용의 확장

C++ 표준 라이브러리는 템플릿을 기반으로 디자인된다. 따라서 템플릿을 잘 알면, 그만큼 라이브러리에 대한

이해도와 활용능력이 향상된다.


1.1 클래스 템플릿과 배열 클래스 템플릿

클래스 템플릿을 기반으로, 템플릿 클래스의 객체를 저장할 때에는 어떻게 생성해야 할까?

클래스 템플릿 기반의 객체 생성에는 일정한 규칙이 존재하기 때문에, 이는 어려운 문제가 아니다.

다음과 같이 객체를 생성하면,

=> BoundCheckArray<int> arr(5);

int형 데이터의 저장이 가능한 것처럼, 저장 대상의 자료형이 템플릿 클래스이니, 다음과 같이 객체를 생성할 수 있다.

=> BoundCheckArray<Point<int>> arr(5);

그리고 저장 대상이 Point<int> 템플릿 클래스의 객체가 아닌, Point<int>형 포인터라면, 다음과 같이 생성하면 된다.

=> BoundCheckArray<Point<int>*> arr(5);

위의 문자은 typedef 선언을 통해서 다음과 같이 구성할 수도 있다.

typedef Point<int>* POINT_PTR;
BoundCheckArray<Point_PTR> arr(5);


1.2 특정 템플릿 클래스의 객체를 인자로 받는 일반함수의 정의와 friend 선언

Point<int>, Point<Double>과 같은 템플릿 클래스의 자료형을 대상으로도 템플릿이 아닌 일반함수의 정의가 가능하고,

클래스 템플릿 내에서 이러한 함수를 대상으로 friend 선언도 가능하다.

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

    friend Point<int> operator+(const Point<int>&, const Point<int>&);
    friend ostream& operator<<(ostream&, const Point<int>&);
};

Point<int> operator+(const Point<int>& pos1, const Point<int>& pos2)
{
    return Point<int>(pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos);
}
ostream& operator<<(ostream& os, const Point<int>& ref)
{
    os << '[' << ref.xpos << ", " << ref.ypos << ']' << endl;
    return os;
}

int main(void)
{
    Point<int> pos1(2, 4);
    Point<int> pos2(3, 5);
    Point<int> pos3 = pos1 + pos2;

    cout << pos1 << pos2 << pos3;
    return 0;
}
[2, 4]
[3, 5]
[5, 9]


2. 클래스 템플릿의 특수화(Class Template Specialization)

함수 템플릿의 특수화와 유사하게, 클래스 템플릿도 특수화를 할 수 있다.


2.1 클래스 템플릿 특수화

함수 템플릿을 특수화하는 이유는 특정 자료형에 대해서 구분이 되는 다른 행동을 보이기 위해서다.

마찬가지로, 클래스 템플릿을 특수화하는 이유는 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는

다른 행동양식을 적용하기 위해서이다. 즉, 클래스 템플릿을 특수화하면, 템플릿을 구성하는 멤버함수의 일부 또는

전부를 다르게 행동하도록 정의할 수 있다. 클래스 템플릿을 특수화하는 방법은 다음과 같다.

template <class T>
class A
{
public:
    T func(T num) {}
};
/* class template specialization */
template <>
class A<int>
{
public:
    int func(int num) {}
};


예제의 위 부분은 일반적인 클래스 템플릿이고, 아랫 부분이 이를 기반으로 자료형 int에 대해 특수화한

템플릿 클래스이다. 다음 예제를 통해 특수화를 진행해보도록 하자.

template <class T>
class Point
{
private:
    T xpos, ypos;
public:
    Point(const T x, const T y) : xpos(x), ypos(y) {}
    void showposition(void) const
    {
        cout << '[' << xpos << ", " << ypos << ']' << endl;
    }
};

template <class T>
class Test
{
private:
    T mdata;
public:
    Test(T data) : mdata(data) {}
    void showdata(void) const
    {
        cout << "data : " << mdata << endl;
    }
};

template <>
class Test<char*>
{
private:
    char *mdata;
public:
    Test(char *data)
    {
        mdata   = new char[strlen(data) + 1];
        strcpy(mdata, data);
    }
    void showdata(void) const
    {
        cout << "String : " << mdata << endl;
        cout << "Length : " << strlen(mdata) << endl;
    }
    ~Test(void)
    {
        delete []mdata;
    }
};

template <>
class Test<Point<int>>
{
private:
    Point<int> mdata;
public:
    Test(const int x, const int y) : mdata(x, y) {}
    void showdata(void) const
    {
        mdata.showposition();
    }
};
int main(void)
{
    Test<int> iwrap(100);
    iwrap.showdata();
    Test<char*> cwrap("Class Template Specialization");
    cwrap.showdata();
    Test<Point<int>> pwrap(10, 20);
    pwrap.showdata();
    return 0;
}
data : 100
String : Class Template Specialization
Length : 29
[10, 20]


위 예제는 char * 형과 템플릿 클래스의 자료형인 Point<int>형 대상의 특수화를 보이고 있다.


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

다음과 같이 정의된 클래스가 있다고 가정해보자.

template <class T1, class T2>
class A {}


여기서 T1과 T2를 각각 char와 int로 하여 특수화를 진행하면 다음의 형태가 된다.

template <>
class A<char, int> {}


다음과 같이 부분적으로 특수화를 진행할 수도 있다.

template <class T1>
class A<T1, int> {}


또한, 부분 특수화와 전체 특수화의 두 가지 모두에 해당하는 객체생성 문장에서는 전체 특수화된

클래스를 대상으로 객체가 생성된다. 즉, 전체 특수화가 부분 특수화보다 우선시 된다.


3. 템플릿 인자
템플릿을 정의할 때 결정되지 않은 자료형을 의미하는 용도로 사용되는 T 또는 T1, T2와 같은 문자를

가르켜 '템플릿 매개변수'라 한다. 그리고 템플릿 매개변수에 전달되는 자료형 정보를 가르켜 '템플릿 인자'라 한다.


3.1 템플릿 매개변수에는 변수의 선언이 가능하다.

다음의 정의를 보면, 템플릿 매개변수의 선언에 마치 함수처럼 변수의 선언이 등장했다.

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


그리고 이를 기반으로 다음의 형태로 객체생성이 가능하다.

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


위의 두 문장에서 템플릿 매개변수 len에 전달된 인자 5와 7은 해당 템플릿 클래스의 상수처럼 사용된다.


매개변수에 전달되는 인자 값이 다르면, 두 템플릿 클래스는 서로 다른 형이다.
즉, 아래 예제를 참고하도록 하자.

int main(void)
{
    A<int, 5> i5arr;
    A<int, 7> i7arr;

    i5arr = i7rr; // compile error

    return 0;
}


이렇듯, 템플릿 매개변수에 값을 전달받을 수 있는 변수를 선언하면, 변수에 전달되는 상수를 통해서

서로 다현 형의 클래스가 생성되게 할 수 있다. 따라서 위 예제의 경우, 길이가 다른 두 배열 객체간의

대입 및 복사에 대한 부분을 신경쓰지 않아도 된다. 만약에 생성자를 이용해서 배열의 길이를 결정하게 했다면,

길이가 같은 배열에 대해서만 대입을 허용하기 위해서 추가적인 코드의 삽입이 불가피하며, 이러한 추가적인

코드는 대입 및 복사의 과정에서 CPU가 수행해야 할 일을 늘리는 결과로 이어진다.


3.2 템플릿 매개변수는 디폴트 값 지정도 가능하다.

함수의 매개변수에 디폴트 값의 지정이 가능하듯이, 템플릿 매개변수에도 디폴트 값의 지정이 가능하다.

그러나 템플릿 매개변수에 디폴트 값이 지정되어도, 템플릿 클래스의 객체생성을 의미하는 '<>' 기호는

반드시 추가되어야 한다. 비록 그 안을 비워둘지라도 말이다.


4. 템플릿과 static

4.1 함수 템플릿과 static 지역변수

딱 한 번 초기화된 상태에서 그 값을 계속 유지하는 static 변수의 특성을 이미 잘 알고 있다고 가정하고

다음 예제를 보도록하자.

template <class T>
void show(void)
{
    static T num = 0;
    num = num + 1;
    cout << num << " ";
}
int main(void)
{
    show<int>();
    show<int>();
    show<int>();
    cout << endl;
    show<double>();
    show<double>();
    show<double>();
    cout << endl;
    show<long>();
    show<long>();
    show<long>();
    cout << endl;

    return 0;
}
1 2 3
1 2 3
1 2 3


실행결과로 볼 수 있듯이, 컴파일러에 의해 만들어진 템플릿 함수 별로 static 지역변수가 유지된다.


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

static 멤버변수는 변수가 선언된 클래스의 객체간 공유가 가능하다. 다음 예제를 보도록 하자.

template <class T>
class Test
{
private:
    static T mem;
public:
    void addmem(int num) { mem = mem + num; }
    void showmem(void) const
    {
        cout << mem << endl;
    }
};

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

int main(void)
{
    Test<int> obj1;
    Test<int> obj2;
    obj1.addmem(2);
    obj2.addmem(3);
    obj1.showmem();

    Test<long> obj3;
    Test<long> obj4;
    obj3.addmem(100);
    obj4.showmem();
    return 0;
}
5
100


위의 예제를 보면, 생성된 템플릿 클래스 별로 static 변수를 공유하는 것을 확인할 수 있다.


4.3 template<class T>와 template<>

단순하게 생각해서, 정의 부분에 T가 존재하면 T를 위한 설명을 위해서 <class T>를 덧붙이고,

그렇지 않으면 <> 의 형태로 간단하게 선언하면 된다.


4.4 템플릿 static 멤버변수 초기화의 특수화

앞서 보인 예제에서는, 모든 static 멤버변수는 0으로 초기화가 된다. 하지만

특정 자료형만 다른 값으로 초기화할 수 있다. 위의 초기화문에 대해서 특수화를 진행하면 된다.

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

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


출처 : 윤성우 열혈 C++ 프로그래밍