본문 바로가기

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

CHAPTER13 - 템플릿(Template) 1

1. 템플릿(Template)에 대한 이해와 함수 템플릿

1.1 함수를 대상으로 템플릿 이해하기

함수 템플릿은 함수를 만들어 낸다. 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야 한다.

즉, '함수 템플릿'이란 함수를 만드는 도구이다. 함수 템플릿이 만들어 내는 함수의 자료형은 결정되어 있지 않다.

int Add(int num1, int num2)
{
    return num1 + num2;
}


위 함수의 기능과 자료형은 다음과 같다.

- 함수의 기능 : 덧셈

- 대상 자료형 : int형 데이터

이러한 함수를 만들어 낼 수 있는 템플릿은 다음과 같이 정의한다.

T Add(T num1, T num2)
{
    return num1 + num2;
}


앞서 정의한 Add 함수와 비교해 보면, int형 선언을 T로 대신했음을 알 수 있는데, 이는 자료형을 결정짓지 않은,

그래서 나중에 T를 대신해서 실제 자료형을 결정하겠다는 뜻이다. 따라서 위 '함수 템플릿'의 기능과 자료형은 다음과 같다.

- 함수의 기능 : 덧셈

- 대상 자료형 : 결정되어 있지 않음

또한, 우리는 컴파일러에게 다음과 같은 메시지를 전달해야 한다.

"T는 자료형을 결경젓지 않겠다는 의미입니다. 즉, 함수를 만들어 내는 템플릿을 정의하기 위해 사용된 것입니다."

그리고 이러한 메시지를 담아서 위의 '함수 템플릿'은 다음과 같이 완성해야 한다.

template <typename T> // class T
T Add(T num1, T num2)
{
    return num1 + num2;
}


위의 예제에 'template <typename T>'라는 문장이, T라는 이름을 이용해서 아래 함수를 템플릿으로 정한다는 의미이다.

typename을 대신해서 class를 사용할 수도 있다.

=> template <class T>

template <typename T> // class T
T Add(T num1, T num2)
{
    return num1 + num2;
}

int main(void)
{
    cout << Add<int>(10, 20) << endl;
    cout << Add<double>(10.22, 20.31) << endl;

    return 0;
}
30
30.53


위의 예제를 보면, '<>'가 등장하고, 그 안에 데이터 타입이 명시된다.

즉, 'T를 괄호 안의 데이터 타입으로 변경하여 만들어진 함수를 호출한다.' 는 의미이다.
그래서 '컴파일러'는 이 문장을 보는 순간 해당 데이터 타입의 형태로 함수를 하나 만든다.

함수가 한 번 만들어지면, 그 다음에는 만들어진 함수를 호출할 뿐 새로운 함수를 만들지는 않는다.

또한, 컴파일 과정에서 만들어지므로, 실행속도에는 신경쓸 요소가 아니다.


추가적으로, '<>' 부분은 생략 가능하다. 예를 들어서 다음의 함수 호출문장을 보면,

=> Add(3.5, 4.5);

컴파일러는 다음과 같이 판단한다.

"전달되는 인자의 자료형이 double형이니, 함수 템플릿의 T가 double이 되어야 손실이 없겠다."


1.2 함수 템플릿과 템플릿 함수

다음 정의를 가르켜 '함수 템플릿(function template)'이라 한다.

template <typename T> // class T
T Add(T num1, T num2)
{
    return num1 + num2;
}


그리고, 위의 템플릿을 기반으로 컴파일러가 만들어 내는 다음 유형의 함수들을 가르켜 '템플릿 함수(template fuction)'

라 한다.

int Add<int>(int num1, int num2)
{
    return num1 + num2;
}
double Add<double>(double num1, double num2)
{
    return num1 + num2;
}


위의 템플릿 함수의 표시에서 '<int>'와 '<double>'은 일반함수가 아닌, 컴파일러가 만들어낸

템플릿 기반의 함수임을 표시한 것이다.

또한, 템플릿 함수는 컴파일러에 의해서 생성된 함수이기 때문에 '생성된 함수(Generated Function)'으로도 불린다.

※ 템플릿 함수는 일반 함수와 동일한 기능과 자료형을 가질지어도 구분된다. ( 동시에 정의가 가능하다. )


1.3 둘 이상의 형(Type)에 대해 템플릿 선언하기

템플릿의 정의에도 다양한 자료형의 선언이 가능할 뿐만 아니라, 둘 이상의 형(Type)에 대해서 템플릿을

선언할 수도 있다.

template <class T1, class T2>
//template <typename T1, typename T2>
void showdata(double num)
{
    cout << (T1)num << ", " << (T2)num << endl;
}

int main(void)
{
    showdata<char, int>(65);
    showdata<short, double>(69.4);

    return 0;
}
A, 65
69, 69.4


1.4 함수 템플릿의 특수화(Specialization)

상황에 따라서 템플릿 함수의 구성방법에 예외를 둘 필요가 있다. 이때 사용되는 것이 '함수 템플릿의 특수화'이다.

template <class T>
T max(T a, T b)
{
    return a > b ? a : b;
}
template <>
char* max<char*>(char* a, char* b)
{
    cout << "char* max<char*>" <<endl;
    return strlen(a) > strlen(b) ? a : b;
}
template <>
const char* max<const char*>(const char* a, const char* b)
{
    cout << "const char* max<const char*>" << endl;
    return strcmp(a, b) > 0 ? a : b;
}
int main(void)
{
    char str1[] = "simple";
    char str2[] = "best";

    cout << max("simple", "Best") << endl;
    cout << max(str1, str2) << endl;

    return 0;
}
const char* max<const char*>
simple
char* max<char*>
simple


위 예제를 보면, 'template<>'로 시작하는 두 개의 함수가 정의 되었다.

각각의 의미는 "char*형 함수는 내가 제시를 하니, char*형 템플릿 함수가 필요한 경우 별도로 만들지 말고

이것을 사용하라.""const char*형 함수는 내가 제시를 하니, const char*형 템플릿 함수가 필요한 경우

별도로 만들지 말고 이것을 사용하라."이다.


2. 클래스 템플릿(Class Template)

앞서 함수를 템플릿으로 정의했뜻이 클래스도 템플릿으로 정의가 가능하다. 그리고 이렇게 정의된 템플릿을 가리켜

'클래스 템플릿(class template)'라 하며,  이를 기반으로 컴파일러가 만들어 내는 클래스를 가르켜

'템플릿 클래스(template class)' 또는 '생성된 클래스(generated class)'라 한다.


2.1 클래스 템플릿의 정의

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

int main(void)
{
    Point<int> p1(10, 20);
    Point<double> p2(10.34, 12.55);

    p1.showposition();
    p2.showposition();
    return 0;
}
[10, 20]
[10.34, 12.55]


클래스 템플릿의 정의방법은 함수 템플릿의 정의방법과 동일하기 때문에, 쉽게 이해가 되었을 것이다.

템플릿 함수를 호출할 때와 다르게, 템플릿 클래스의 객체를 만들 때에는 '<>'를 생략할 수 없다.


2.2 클래스 템플릿의 선언과 정의의 분리

클래스 템플릿도, 멤버함수를 클래스 외부에 정의하는 것이 가능하다.

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

template <class T>
Point<T>::Point(T x, T y) : xpos(x), ypos(y) {}

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


함수 정의에서 'Point<T>'가 의미하는 것은, "T에 대해 템플릿화 된 Point 클래스 템플릿" 이다.

그리고 간혹 함수를 외부에 정의하는 과정에서 'template<class T>'를 빼먹는 경우가 있는데, 이러한 경우

컴파일러는 T가 무엇인지 모르기 때문에 에러 메시지를 전달한다. 클래스 템플릿의 정의와 함수의 정의가

완전히 별개이기 때문에 각각에 대해서 문자 T가 무엇을 의미하는지 설명해야 한다.


1.3 파일을 나눌 때에 고려할 사항

컴파일은 파일단위로 이뤄진다는 사실을 이미 알고 있을 것이다.

int main(void)
{
    Point<int> p1(10, 20);
    Point<double> p2(10.34, 12.55);
    Point<char> p3('P', 'F');

    return 0;
}


위의 main 함수가 정의된 소스가 컴파일 될 때, 컴파일러는 총 3개의 템플릿 클래스를 생성해야 한다.

그리고 이를 위해서는 클래스 템플릿인 Point의 모든 것을 알아야 한다. 즉, 컴파일러는 헤더파일에

담긴 선언 뿐만 아니라, .cpp 파일에 담긴 정의도 필요하다. 그런데 main 함수가 정의된 소스 파일에서

.cpp 파일에 담긴 정보를 참조할 만한 선어니 되어있지 않다면 에러가 발생하게 된다.

두 파일이 동시에 컴파일되는 것은 맞지만, 서로 다른 소스파일이기 때문에, 그리고 파일 단위 컴파일 원칙에 의해서

하나의 파일을 컴파일하면서 다른 파일은 참조하지 않는다.

해결책으로는 다음과 같은 두 가지 방법이 있다.

(1) 헤더파일에 템플릿 클래스의 정의를 모두 포함시킨다.

(2) 메인 함수가 정의된 소스 파일에, 클래스 템플릿이 정의된 소스파일을 포함시킨다.


1.4 배열 클래스의 템플릿화

class BoundCheckIntArray {};
class BoundCheckPointArray {};
class BoundCheckPointPtrArray {};


위의 세 클래스에 대해 템플릿을 정의해 보자.


 arrytemplate.h
#ifndef __ARRAY_TEMPLATE_H_
#define __ARRAY_TEMPLATE_H_

#include <iostream>
using namespace std;

template <class T>
class BoundCheckArray
{
private:
    T *arr;
    int arrlen;

    BoundCheckArray(const BoundCheckArray& arr) {}
    BoundCheckArray& operator=(const BoundCheckArray& arr) {}
public:
    BoundCheckArray(int);
    T& operator[](int);
    T operator[](int) const;
    int getarrlen(void) const;
    ~BoundCheckArray(void);
};

template <class T>
BoundCheckArray<T>::BoundCheckArray(int len) : arrlen(len)
{
    arr = new T[len];
}

template <class T>
T& BoundCheckArray<T>::operator[](int idx)
{
    if(idx < 0 || idx >= arrlen)
    {
        cout << "Array index out of bound exception" << endl;
        exit(1);
    }
    return arr[idx];
}

template <class T>
T BoundCheckArray<T>::operator[](int idx) const
{
    if(idx < 0 || idx >= arrlen)
    {
        cout << "Array index out of bound exception" << endl;
        exit(1);
    }
    return arr[idx]
}

template <class T>
int BoundCheckArray<T>::getarrlen(void) const
{
    return arrlen;
}

template <class T>
BoundCheckArray<T>::~BoundCheckArray(void)
{
    delete []arr;
}

#endif
 point.h
#ifndef __POINT_H_
#define __POINT_H_

#include <iostream>
//using namespace std;
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(void) {}
    Point(int, int);
    friend ostream& operator<<(ostream&, const Point&);
};

#endif
 point.cpp
#include <iostream>
#include "point.h"
using namespace std;

Point::Point(int x = 0, int y = 0) : xpos(x), ypos(y) {}

ostream& operator<<(ostream& os, const Point& ref)
{
    os << '[' << ref.xpos << ", " << ref.ypos << ']' << endl;
    return os;
}
 main.cpp
#include <iostream>
#include "arraytemplate.h"
#include "point.h"
using namespace std;

int main(void)
{
    // array of the integer
    BoundCheckArray<int> iarr(5);
    for(int i = 0 ; i < iarr.getarrlen() ; i ++)
    {
        iarr[i] = (i + 1) * 11;
    }
    for(int i = 0 ; i < iarr.getarrlen() ; i ++)
    {
        cout << iarr[i] << endl;
    }

    // array of the Point class
    BoundCheckArray<Point> oarr(3);
    for(int i = 0 ; i < oarr.getarrlen() ; i ++)
    {
        oarr[i] = Point(i + 1, i + 1);
    }
    for(int i = 0 ; i < oarr.getarrlen() ; i ++)
    {
        cout << oarr[i];
    }

    // array of the pointer
    typedef Point* POINT_PTR;
    BoundCheckArray<POINT_PTR> parr(3);
    for(int i = 0 ; i < parr.getarrlen() ; i ++)
    {
        parr[i] = new Point(i + 10, i + 10);
    }
    for(int i = 0 ; i < parr.getarrlen() ; i ++)
    {
        cout << *parr[i];
    }

    delete parr[0];
    delete parr[1];
    delete parr[2];
    return 0;
}


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