본문 바로가기
C C++ - STL

STL - 3 템플릿에 대하여

by violetoz 2014. 2. 13.


[STL-3]Template에 대하여|STL을 배우자
2004.05.19 20:13

출처C언어를 배우자 | 라온

원문http://cafe.naver.com/cafec/916

아무래도 STL을 사용하기 위해선 Template을 자세히 알고 가야 할 것 같아 검색으로 찾은 자료를 올려 놓습니다. 한 번 읽어 보시고 Template으로 간단한 예제를 만들어 보세요.

 

출처는 http://cit.yc.ac.kr/~sjkim/re/C/book_list1.htm  입니다.

현재 사용되는 표준라이브러리로 설명한 것이 아니기 때문에 헤더에 차이가 있습니다.

일반적인 입문서 Teach your self C++ 같은 책의 Template 내용을 참고하셔도 좋습니다.

 

다음 강좌 [4]에서는 Container에서 꼭 필요한 Iterator 대해서 살펴보도록 하겠습니다.

 

========================================================================================

10장 템플릿

 이 장에서는 템플릿(Template)의 기본적인 개념과 함께 템플릿을 이용하여 함수와 클래스를 정의하는 방법과 그 외에 템플릿과 관련된 여러 사항들에 대해 살펴보도록 한다.

 

10.1 템플릿의 기본 개념

   

  본론으로 들어가기 전에 간단히 템플릿에 대한 얘기를 한 번 해보자.

템플릿이란 의미 그대로 해석한다면 틀이라는 단어로 해석할 수 있다. 틀이란 어떤 일정한 형태로 이루어져 있는 모양이라고 할 수 있을 것이다.

  가령 붕어빵 기계를 예로 들어보자. 우리가 만드는 붕어빵은 밀가루, 팥, 설탕 등과 같은 재료로 구성되어 있고 이것을 붕어빵을 굽는 기계(틀)에 넣고 구으면 완성된 붕어빵이 나온다. C++의 틀도 이런 것과 거의 다르지 않다. 틀이라는 이름도 이런 사실에서부터 유추되었다고 할 수 있다.


  이제 본론으로 들어가서 템플릿에 대하여 알아보자. 템플릿은 함수 혹은 클래스가 어떻게 만들어 질 것인가를 함수나 클래스의 인수를 사용하여 지정하는 것이다. 함수 중복이 이름만을 공유하는 수준에서 그치는 반면, 템플릿은 클래스 정의나 함수 정의 부분 같은 실제 코드까지도 정의하기 때문에 보다 더 높은 수준의 다형성을 지원한다.


  먼저 다음에 나오는 함수들을 살펴보자.


        void max(int a, int b);

        void max(long a, long b);

        void max(double a, double b);

        ...


  이 함수들은 함수 이름은 같지만 인수의 형이 각각 다르다. 이렇게 정의된 함수 max()를 호출 시 컴파일러는 인수의 인수형을 인식하고 정확한 함수를 호출할 수 있게 된다. 그러나 비록 이름이 같다고 해도 그 함수가 인수형은 같지만 중복되는 코드가 많다면 문제가 될 수 있다.  프로그램에서 함수의 코드가 짧다면 별로 문제될 것이 없겠지만 함수의 코드가 길어진다면 문제가 생긴다. 코드가 길다면 비슷한 부분이 많아져서 별로 보기도 좋지 않고 함수의 내용이 바뀌기라도 한다면 모든 함수 부분을 고쳐야 하는 불편함이 생기게 된다.

  이렇게 ‘서로 다른 자료형 인수에 대해 동일한 내부 구조를 가진 함수나 클래스들을 반복 코딩하지 않고 한번만 정의하는데 사용되는 틀’이 바로 템플릿이라고 할 수 있다.


  템플릿으로 어떤 함수나 클래스를 정의하면 전체적으로는 유사할지 몰라도 내부적으로 약간의 차이가 있는 몇몇 함수나 클래스가 컴파일러에 의해 자동 생성된다. 따라서 사용자가 클래스나 함수를 템플릿으로 한번 정의만 해 놓으면 비슷한 구조를 가지는 개개의 함수나 클래스를 매번 정의하지 않아도 되기 때문에 상당히 편리하다.

  템플릿의 기능은 다음 그림과 같이 나타낼 수 있다.





그림 10.1 템플릿의 기능


10.2 함수 템플릿(Function Template)


  위에서 템플릿이란 동일한 내부 구조를 가진 함수들을 한꺼번에 나타내려고 하는 것이라고 하였다. 그리고 함수 템플릿은 인수형이 다른 함수들의 모임이라고 할 수 있다. 함수 템플릿은 이름이 같은 여러 함수를 각각 따로 정의하지 않고 한꺼번에 정의할 수 있으므로 함수의 구현 차원에서 많은 도움이 된다.

  

  다음 예제는 함수 템플릿의 예를 설명한 것으로서 함수 템플릿 설명 시 자주 인용되는 예제이다.


<예제 10.1>

#include <iostream.h>

#include <conio.h>


int max(int a, int b)                    // int 형의 함수 max()  

{

        return a>b ? a : b ;    

}


long max(long a, long b)                 // long 형의 함수 max()

{

        return a>b ? a : b ;

}


double max(double a, double b)          // double 형의 함수 max()

{

        return a>b ? a : b ;

}


void main()

{

        cout << max(5, 6) << endl;

        cout << max(1234, 2345) << endl;

        cout << max(3.12, 8.15) << endl;

}

 

  이 프로그램의 실행 결과는 다음과 같다.


        6

        2345

        8.15


  위의 예제에서 세 개의 함수 max()는 인수의 자료형이나 반환값만 다르고 함수 내부의 소스 코드는 전부 일치한다. 이런 함수들이 함수들 자체의 코드가 같다는 것은 이 함수들 간에 유사성이 존재한다는 것을 의미하지만 함수 중복은 이런 유사성을 인식하지 못한다. 그래서 매크로를 이용하는 방법도 있지만 역시 문제가 발생하는 부분이 생긴다.

  예를 들어 다음 코드와 같이 정의하는 것은 가능하다.


        #define max(x, y)   (((x) > (y)) ? (x) : (y))

     

하지만 다음과 같이 코드를 작성한다면 오류가 발생하게 될 것이다.


       #define max(k++, 7)  (((k++) > 7)) ? (k++) : (7))


  이것은 변수 k의 값이 일정하지 않게 되어 발생하는 오류라고 할 수 있다.

각각의 인수가 두 번 계산이 되기 때문에 max(k++, 7)와 같이 증가 연산자가 실인수에 사용되면 k의 값에 대해 증가 연산자가 두 번 계산된다. 그러므로 오류를 발생하게 될 것이다.

  이런 경우를 해결할 수 있는 방법이 바로 함수 템플릿이다. 템플릿은 함수가 어떻게 만들어질지를 인수형이나 인수로서 지정하는 것이라고 위에서 언급하였다. 즉, 세 개의 함수 max()에서 서로 다른 부분인 반환값과 인수형을 템플릿의 인수로 대체한다.

  다음 코드는 <예제 10.1>을 템플릿을 사용하여 만든 예제이다.


<예제 10.2>

#include <iostream.h>

#include <conio.h>


template <class T>                     // 템플릿 함수 max()의 정의

T max(T a, T b)

{

        return a>b ? a : b ;

}       

        

void main()

{

        cout << max(5, 6) << endl;

        cout << max(1234, 2345) << endl;

        cout << max(3.12, 8.15) << endl;

}


  <예제 10.1>과 비교해 보면 코드가 많이 줄어들었음을 알 수 있다. max()라는 세 개의 함수에서 반복되는 부분은 그냥 두고 서로 다른 부분(인수형, 반환값)을 T로 대체할 수 있다. 

  함수 템플릿의 정의와 관련된 사항들은 다음절에서 자세히 알아보도록 하겠다.  위 프로그램의 실행 결과는 <예제 10.1>과 같다.


10.2.1 함수 템플릿의 정의


  함수 템플릿은 템플릿 인수가 반드시 자료형 인수이어야 한다. 자료형 인수란 <예제 10.2>에 나온 <class T>의 T와 같이 키워드 class 다음에 있는 인수형을 말하는 것이며, 이 T는 어떤 자료형을 나타내는 것이다(사용자 정의 자료형 포함). 키워드 class는 클래스를 정의할 때 나타내는 키워드 class와는 관계가 없으며 단지 자료형 인수임을 나타내기 위한 것이다.

  자료형 인수(여기서는 T)들은 함수의 반환값이나 자료형, 지역 변수의 자료형을 나타내게 된다.

다음은 함수 템플릿의 정의 형식이다.


<형식 10.1>

        template<템플릿_인수리스트>

        반환값 함수템플릿_이름

        {

                함수템플릿_본체

        };


  앞의 예제에서 템플릿으로 구현된 부분을 살펴보면,


template <class T>

T max(T a, T b)

{

        return a>b ? a : b;

}


  여기서 max()라는 템플릿 함수는 반환값과 자료형이 모두 자료형이 T라는 템플릿 인수로 되어 있다. 물론 여기서 지역 변수는 나오지 않았지만 당연히 지역 변수의 자료형도 T로 될 것이다.

  하나 이상의 자료형 인수를 템플릿 인수로 사용할 수 있다. 그러나 템플릿 인수 리스트에 모든 자료형 인수가 최소한 한 번씩은 쓰여야 한다. 그렇지 않으면 오류가 발생한다. 또한 모든 템플릿 인수 앞에는 반드시 키워드 class가 붙어야 한다.

  다음은 템플릿의 올바른 사용과 잘못된 사용의 예이다.


        template <class T, class U>

        T aaa(T a, U b);              // 옳음


        template <class T, class U>

        T aaa(U a, U b);             // 오류발생(T가 반환값에만 쓰였음)

                                           

        template <class T, class U>

        T aaa(T a, T b);           // 오류발생(템플릿 인수 U는 쓰이지                                         않았음)


        template <class T, U>

        T aaa(T a, U b) ;            // 오류발생(키워드 class가 쓰이지

                                       않았음)


  일반 함수에서는 외부 호출을 할 수 있는 키워드 extern, 확장 함수를 나타내는 inline 함수 그리고 정적 함수를 나타내는 static 함수 같은 키워드들을 함수의 앞에 붙여서 사용했었다. 템플릿 함수도 그와 마찬가지로 템플릿 함수명의 앞에 기술해야 한다.

  다음은 키워드 extern, inline, static의 사용 예이다.


        template <class T, class U>

        extern T aaa(T a, U b);


        template <class T, class U>

        inline T aaa(T a, U b);


        template <class T, class U>

        static T aaa(T a, U b);


  보통 외부 함수나 클래스를 정의할 때는 헤더 파일에서 정의한 다음, 소스 파일에서 그 함수나 클래스를 사용한다. 이와 마찬가지로 템플릿을 정의할 때 헤더 파일에서 정의한 다음, 소스파일에서 그 함수 템플릿이나 클래스 템플릿들을 사용할 수 있다. 물론 템플릿을 정의한 헤더 파일은 소스파일에 포함시켜야 함은 당연한 것이다.

  다음은 헤더에서 템플릿을 정의한 후 소스파일에서 사용하는 예이다.


<max.h 파일>                          <test.cpp>

          


10.2.2 템플릿 함수의 생성


  함수 템플릿이 정의되면 컴파일러는 템플릿 인수의 값에 따라서 템플릿 함수를 생성한다. 함수 템플릿이 정의되었다고 템플릿 함수가 생성된 것은 아니며 템플릿 함수가 생성되기 위해서는 소스 코드 내에서 그 템플릿 함수를 호출해야하며 호출이 이루어짐과 동시에 템플릿 함수가 생성된다.

  다음은 템플릿 함수의 생성에 관한 예제이다.


<예제 10.3>

#include <iostream.h>

#include <conio.h>


template <class T>

T max(T a, T b)               // 템플릿 함수 max()의 정의

{

        return a>b ? a : b;

}       

        

void main()

{

        // 템플릿 함수 max(int, int)가 생성되고 호출됨

        int i  = max(3, 8); 

        

        // 템플릿 함수 max(double, double)가 생성되고 호출됨

        double j = max(2.14, 3.14);


        cout << i << endl;

        cout << j << endl;

}

 

  이 프로그램의 실행 결과는 다음과 같다.


        8

        3.14


  여기서 한가지 체크하고 넘어가야 할 것은 함수 템플릿의 정의에 나타나는 자료형 인수의 자료형을 어떻게 아는가 하는 것이다. 그것은 함수 호출에 사용된 실인수의 자료형에서 알아낼 수 있다. 

가령 함수 템플릿의 인수가 다음과 같이 선언되었다고 하자.


        T  a      // 자료형이 T인 a


  이 함수 템플릿이 생성되어 호출되었을 때 그 때의 실인수 자료형이 int 형이었다면 T는 int가 된다.

그런데 다음과 같이 선언되었을 때,


        T* a      // 자료형이 T*인 a


  호출되는 실인수의 자료형이 int였다면 어떻게 될까?

이때는 실인수의 자료형 T*와 int는 일치하지 않는다. 그러므로 오류가 발생할 것이다. 만약 하나의 자료형 인수가 함수 템플릿의 인수 리스트에서 여러 번 사용된 경우는 각 경우에 구해진 값이 정확히 일치해야 한다.

  다음에 나오는 예를 살펴보자.


<예제 10.4>

#include <iostream.h>

#include <conio.h>


template <class T>

T max(T a, T b)                       // 템플릿 함수 max()의 정의

{

        return a>b ? a : b;

}       

        

void main()

{

        // 템플릿 함수 max(int, int)가 생성되고 호출됨

        int i  = max(3, 8); 

         

        // 오류발생 : 템플릿 함수 max(int, double)가 생성되고 호출됨

        double j = max(3, 3.14);


        cout << i << endl;

        cout << j << endl;

}


  위의 코드는 오류를 가지고 있다. 앞의 설명을 잘 보았다면 무엇이 틀렸는지를 알 수 있을 것이다. 함수 main()의 두 번째 줄에서 두 인수의 자료형이 일치하지 않기 때문(int 형과 double 형이 같이 쓰였음)에 오류가 발생하게 된다. 이런 오류를 수정하려면 어떻게 해야 할까? 이런 경우에는 자료형 변환을 해야하는데 템플릿 함수는 자료형 변환을 자동으로 변환하지 못한다. 그러므로 자료형 변환을 하려면 double 형에 대한 템플릿 함수의 원형을 정의해 주어야 한다.

  다음은 위의 예제를 수정한 것이다.


<예제 10.5>

#include <iostream.h>

#include <conio.h>


template <class T>

T max(T a, T b)

{

        double max(double, double);     // double 형에 대한

                                        // 템플릿 함수의 원형 선언

        return a > b ? a : b;

}       

                        

                             

void main()

{

        // 템플릿 함수 max(int, int)가 생성되고 호출됨

        int i  = max(3, 8); 

         

        // 위의 선언에 의해서 max((double) 3, 3.14)로 변환됨        

        double j = max((double)3, 3.14);


        cout << i << endl;

        cout << j << endl;

}

 

이 프로그램의 실행 결과는 다음과 같다.


        8

        3.14


10.2.3 함수 템플릿의 중복


  일반 함수는 인수 리스트가 구별되기만 하면 중복이 허용되었다. 함수 템플릿도 이와 마찬가지로 성립이 되지만 약간의 차이가 있다.

다음 예를 살펴보자.


        template <class T> T max(T a, T b);      

        template <class T> T max(T* a, T* b);


  위의 예들은 각 템플릿 함수간의 인수 리스트가 다르기 때문에 중복이 허용되는 경우이다.

  그러나 다음 예들은 중복이 허용되지 않는다.


        template <class U> U max(U a, U b);

        template <class V> void max(V a, V b);


  위의 예들은 인수 리스트가 이름만 다르거나 반환값이 다른 경우이므로 이런 경우에는 각 템플릿 인수는 구별되지 않는다. 그러므로 당연히 오류이다.

  그리고 인수 리스트가 다른 경우에도 다음 예와 같이 특정 함수에 관해서는 경우에 따라 약간의 모호성이 나타나므로 사용하기가 어려운 경우도 있다.


        template <class T> T max(T a, T b);      

        template <class T> T max(T* a, T* b);

        int* max(int* a, int* b);


  다음 예제는 함수 템플릿과 일반 함수의 중복에 관해서 다룬 것이다.


<예제 10.6>

#include <iostream.h>

#include <string.h>


template <class T> T max(T a, T b)           

{

        char* max(char* a, char* b);            

        return (a>b) ? a : b;

}


void main()

{

        char* k;

        k = max("one", "two");

        cout << k << endl;

}


    이 프로그램의 실행 결과는 다음과 같다.


        two


  위 예제에서 사용된 관계 연산자 >는 실제 내용을 비교하는 것이 아니라 포인터 값만 비교하므로 k의 값은 알 수 없다. 그러므로 문자열의 실제 내용을 비교하는 max()를 알아내려면 문자열에 대한 max()를 따로 정의해 주어야 한다.


<예제 10.7>

#include <iostream.h>

#include <string.h>


template <class T> T max(T a, T b)           

{

        return (a>b) ? a : b;

}


char* max(char* a, char* b);            // 문자열 함수 max() 선언


void main()

{

        char* k;

        k = max("one", "two");          

        cout << k << endl;

}


char* max(char* a, char* b)             // 문자열에 대한 함수 max()

{

        return (strcmp(a,b) > 0 ) ? a : b;

}


  위의 예제에서 k = max("one", "two")에서 일반 함수를 호출하고 있다. 그런데 함수 템플릿과 중복된 함수는 같은 파일 내에 정의되어야 한다. 그렇지 않고 다른 파일 내에 존재한다면 실제 코드는 함수 템플릿으로부터 생성된다. 

  이 프로그램의 실행 결과는 <예제 10.6>과 같다


  다음은 위에서 설명한 내용을 담은 예제이다.


<예제 10.8>

// Test.cpp


#include <iostream.h>

#include <string.h>


template <class T> T max(T a, T b)

{

        return (a>b) ? a : b;

}


char* max(char* a, char* b);


void main()

{

        char* k;

        k = max("one", "two");          // 실제 코드는

                                        // 함수 템플릿으로부터 생성

        cout << k << endl;

}


// Test2.cpp

// 함수 템플릿의 중복된 함수가 다른 파일 내에 정의

// 이 함수는 test.cpp에서 쓰이지 않음

char* max(char* a, char* b)

{

        return (strcmp(a,b) > 0 ) ? a : b;

}


  위의 예는 함수 템플릿과 중복되는 함수가 다른 파일 내에 정의되어 있는 경우이다. 따라서 k = max("one", "two")는 Test2.cpp에서 정의된 함수가 호출되는 것이 아니고 Test.cpp의 함수 템플릿에서 템플릿 함수 max(char*, char*)가 호출된다.

  이렇게 템플릿 함수가 다른 템플릿 함수나 일반 함수가 중복 정의되면 이를 구별할 수 있어야 할 것이다.

   이 프로그램의 실행 결과는 <예제 10.6>과 같다


10.3 클래스 템플릿(Class Template)


  클래스 템플릿도 함수 템플릿을 만드는 것과 마찬가지의 방식으로 만들 수 있다. 클래스는 멤버변수와 멤버함수, 그리고 생성자와 소멸자를 가지고 있는 것이 함수와 다른 점이라 할 수 있다. 역시 클래스도 일부만 다르고 대부분이 비슷한 형태로 중복될 수 있다. 이런 부분들을 클래스 템플릿으로 만들어 편리하게 사용할 수 있다.

  다음 예제를 살펴보자.


<예제 10.9>

#include <iostream.h>

#include <string.h>


class intdata                            // 클래스 intdata의 정의

{

protected:

        int x, y;

        int data;

public:

        intdata(int ix, int iy, int idata)   // int 형 데이터 입력함수

        {

                x = ix;

                y = iy;

                data = idata;

        }

        

        void outdata(void)               // int 형 데이터 출력 함수

        {

        

                cout << data << endl;

        }

};


class chardata                          // 클래스 chardata의 정의

{

protected:

        int x , y;

        char data;

public:

        chardata(int ix, int iy, char idata)    // char 형 데이터 입력함수

        {

                x = ix;

                y = iy;

                data = idata;

        }


        void outdata(void)               // char 형 데이터 출력함수

        {

        

                cout << data << endl;

        }

};


class realdata                           // 클래스 realdata의 정의

{

protected:

        int x , y;

        float data;

public:

        realdata(int ix, int iy, float idata)     // 실수형 데이터 입력 함수

        {

                x = ix;

                y = iy;

                data = idata;

        }


        void outdata(void)                   // 실수형 데이터 출력 함수

        {

                cout << data << endl;

        }

};


void main()

{

        intdata int1(10, 10, 12);

        chardata char1(15, 15, 'S');

        realdata real1(30, 10, 3.14159265);

        int1.outdata();

        char1.outdata();

        real1.outdata();

}

  위 프로그램의 실행결과는 다음과 같다


        12

        S

        3.141592


  위의 예제는 특별한 내용은 없다. 평범한 클래스이며 화면상의 좌표에 출력될 정수값을 멤버변수로 가지며 생성자 함수와 멤버함수를 가진다. intdata와 마찬가지로 chardata, realdata도 같은 내용으로 되어 있다. 다른 것은 멤버변수의 타입과 생성자가 조금씩 다를 뿐이다.

  앞의 예제를 클래스 템플릿으로 바꾸어 놓으면 다음과 같다.


<예제 10.10>

#include <iostream.h>

#include <string.h>


template <class T>     

class somedata                        // 클래스 템플릿 somedata의 정의

{

protected:

        int a, b;

        T data;

public:

       somedata(int ix, int iy, T idata)  // 생성자

        {

                a = ix;

                b = iy;

                data = idata;

        }

        

        void outdata(void)               // 데이터 출력 함수

        {

                cout << data << "\n";

        }

};


void main()

{

        // int 형 somedata 클래스 템플릿 생성

        somedata<int> int1(10, 10, 12);  

        

        // char 형 somedata 클래스 템플릿 생성

        somedata<char> char1(15, 15, 'S');


        // float 형 somedata 클래스 템플릿 생성

        somedata<float> real1(30, 10, 3.141592);

        

        int1.outdata();

        char1.outdata();

        real1.outdata();

}


위 프로그램의 실행결과는 다음과 같다


        12

        S

        3.141592



  클래스 템플릿은 주로 적재 클래스를 정의하는데 주로 쓰이게 된다. 적재 클래스란 스택(stack)이나 큐(queue), 연결 리스트(linked list) 등의 자료 구조가 이에 해당되는데 어떤 자료형이 있으면 그 자료형을 가지는 객체들을 내부에 포함하면서 연산을 하는 클래스를 말하는 것이다. 이들로 정의되는 연산들은 객체의 자료형과는 관계없는 추상적인 것이다.


  아래에서 간단히 스택에 관한 클래스의 예제를 살펴보자.  


<예제 10.11 >

// intstack.h 헤더 파일

const int stkSize  = 100;


class stack

{

protected:

        int* stk;

        int stkPointer;

public:

        stack(int st = stkSize)           // 생성자 함수

        {

                stk = new int[st];       // 기억 장소 할당

                stkPointer = 0;          // 스택의 초기화

        }

        ~stack() { delete[] stk;  }       // 소멸자 함수

        void push(int i) { stk[stkPointer++] = i; }   // 첨가 연산  

        int pop() { return stk[--stkPointer]; }       // 삭제 연산

        int top() const { return stk[stkPointer-1]; }  // 꼭대기 요소

        int size() const { return stkPointer; }   // 현재 스택의 크기

};

 

  위의 예제는 스택의 구조를 프로그램(클래스)으로 옮긴 것이다. 스택은 LIFO(Last In First Out)구조로 되어 있다. 한쪽 끝에서만 새로운 자료가 첨가될 수 있고 빠져나갈 수 있는 선형 자료 구조이다. 보통은 접시를 쌓아둔 것에 비교를 하는데 그것을 생각해보면 쉽게 이해할 수 있을 것이다. 맨 위의 접시를, 다시 말하여 맨 위의 자료를 꼭대기(top)라 하고 새로운 자료를 기존의 스택에 첨가하는 연산을 push, 꼭대기 자료를 제거하는 삭제 연산을 pop이라고 한다. 



(가) 스택의 현 상태               (나) push(D)                   (다) pop()             

              그림 10.1  메모리 내의 스택 상태


  스택은 자료형을 구별하지 않는다. 따라서 int 형의 스택도 있을 수 있고 double 형의 스택도 있을 수 있다. 위의 예제는 int 형의 스택구조이다.



  다음은 stack 클래스를 이용한 예제 프로그램이다.


<예제 10.12>

// intstack.cpp

#include <iostream.h>

#include "intstack.h"

        

void main()

{

        stack intStack;

        

        // 스택에 1부터 10까지의 수를 차례대로 첨가.

        for (int i = 1; i <= 10; i++)

        {                     

                intStack.push(i);

        }

        // 스택에 저장된 모든 수를 차례로 삭제, 출력

        for(int j = 0; intStack.size(); j++)          

        {

                cout << "Stack[" << j << "] = " << intStack.pop() << "\n";

        }

}

  이 프로그램의 실행 결과는 다음과 같다.


        stack[0] = 10

        stack[1] = 9

        stack[2] = 8

        stack[3] = 7

        stack[4] = 6

        stack[5] = 5

        stack[6] = 4

        stack[7] = 3

        stack[8] = 2

        stack[9] = 1


  double 형의 스택도 역시 자료형만 다를 뿐 프로그램의 소스는 같을 것이다. 역시 여기서도 클래스 템플릿이 사용될 것이라는 것을 쉽게 알 수 있을 것이다. 그런데 클래스는 이름이 중복되는 것을 허용하지 않는다. 함수는 실인수로서 함수를 구별하지만 같은 이름의 클래스는 둘 이상 존재할 수 없다. 자료형에 따라 처리될 자료의 다른 클래스들이 정의되어 있어야 한다.

  예를 들어 다음과 같은 스택형 자료형들이 있을 수 있다.


        class intStack;        // int 형 스택

        class doubleStack;    // double 형 스택

        class floatStack;      // float형 스택

         ...


  이런 클래스의 내부 구조는 앞에서 설명한 것과 같이 클래스 정의 자체는 변함없지만 멤버함수와 멤버변수의 자료형을 제외하면 각 자료형에 대해 클래스의 내부 정의는 같게 된다. 따라서 매번 클래스들을 정의하는 불편함이 생긴다.

  이것을 클래스 템플릿을 써서 다음 예제에서 고쳐보도록 하자.


<예제 10.13>

// intstack.h

const int stkSize  = 100;

template <class T>

class stack                      // 클래스 stack 정의

{

protected:

        T* stk;

        T stkPointer;

public:

        stack(T st = stkSize)     // 생성자 함수

        {

                stk = new T[st];       // 기억 장소 할당

                stkPointer = 0;         // 스택의 초기화

        }


        ~stack() { delete[] stk;  }      // 소멸자 함수

        void push(T i) { stk[stkPointer++] = i; }   // 첨가 연산  

        T pop() { return stk[--stkPointer]; }       // 삭제 연산

        T top() const { return stk[stkPointer-1]; }  // 꼭대기 요소

        T size() const { return stkPointer; }    // 현재 스택의 크기

};

  

  위의 클래스 템플릿에 의해서  다음과 같이 특정 자료형의 스택이 생성될 수 있다. 


        stack <int> intStack;         // int 형 스택 생성

        stack<double> doubleStack;   // double 형 스택 생성

        stack<float> floatStack;       // float 형 스택 생성

        ...


10.3.1 클래스 템플릿의 정의


  클래스 템플릿도 함수 템플릿과 마찬가지로 정의된다. 단지 틀린 점은 함수와 클래스간의 형식 차이라는 것이다.


<형식 10.2 >

        template<템플릿_인수리스트>

        class 클래스템플릿_이름

        {

                클래스템플릿_멤버정의

        };


  일반적인 클래스 선언과 동일하지만 템플릿 클래스를 정의하는 것이므로 클래스 선언부에 템플릿 인수리스트가 붙게 된다. 나머지는 함수 템플릿을 만들 때와 동일하다.

  템플릿을 사용하여 선언된 클래스를 템플릿 클래스라 하며 템플릿 클래스로 객체를 정의하는 형식은 다음과 같다.


<형식 10.3 >

template_class <템플릿_인수리스트> 객체;


  템플릿 인수 리스트에는 주로 자료형이 들어가게 되지만 상수로 된 식이나 함수도 가능하다.

  다음은 기본적인 자료형으로 이루어진 예제 프로그램을 살펴보기로 하자.


<예제 10.14 >

// test3.cpp

#include <iostream.h>


template <class T>

class  TObject {                 // 입력된 자료 값을 출력해주는 함수         

private:

        T data;

public:

        TObject(T n);

        void View();

};


template <class T>

TObject<T>::TObject(T n)      // 생성자 함수

{

        data = n;

}

        

template <class T>

void TObject<T>::View()       // 출력 함수

{

        cout << data << endl;

}


void main()

{

        TObject<int> obj(25);

        obj.View();

        TObject<char> i('k');

        i.View();

        TObject<double> j(1.25);

        j.View();

}


  이 프로그램의 실행 결과는 다음과 같다


        25

        k

        1.25


  각 객체의 자료형이 클래스 템플릿에 의해 다르게 될 것이다. 하나의 클래스 템플릿으로 여러 가지의 자료형을 나타낼 수 있다.



10.3.2 템플릿 클래스(Template class)의 생성


  클래스 템플릿이 정의되면 필요한 클래스를 생성할 수 있다. 위의 예제에서도 나와있듯이 코드 위 부분에 클래스 템플릿이 정의되어 있다. 이 클래스 템플릿으로부터 생성되는 클래스를 템플릿 클래스라고 한다. 이 템플릿 클래스를 메인 프로그램에서 사용하기 위해서는 템플릿 클래스가 뜻하는 실제 자료형을 지정해야 한다.


  템플릿 함수의 생성은 호출이나 선언에 나타난 실인수의 자료형으로부터 템플릿 인수의 값을 유추해서 이루어지지만 템플릿 클래스는 인수를 사용하지 않으므로 생성하는 방식이 약간 다르다.


        TObject<int> obj(25);

 

  이와 같은 방식으로 선언을 하면 범용 템플릿 클래스가 TObject<int> 라는 정수를 다루는 클래스로 변하게 된다. 문자형을 바꾸는 것과 다른 자료형으로 바꾸는 것도 이와 동일하다.


        TObject<char> i('k');

        TObject<double> j(1.25);


  이와 같은 것은 배열에서도 그대로 적용될 수 있다.  같은 자료형의 많은 자료를 저장할 때 우리는 배열을 쓴다. 배열에 대해서도 기본 자료형에서와 마찬가지의 문제가 발생하는데 그것을 템플릿 클래스로 역시 만들 수 있다.

  다음 예제를 살펴보도록 하자.


<예제 10.15 >

// 배열을 다루는 템플릿 클래스

#include <iostream.h>

        

template <class U>

class UArray                   // 클래스 템플릿 UArray 정의

{

private:

       U* data;

       int size;

public:

       UArray(int k);

       void InOut();

       void View();

};


template <class U>

UArray<U>::UArray(int k)      // 생성자 함수

{

        data = new U[k];       // 입력받은 k값만큼 배열 생성 

        size = k;

}


template <class U>

UArray<U>::InOut()            

{

        int i;


        for(i = 0; i < size; i++)

        {

                cout << "Enter a number : ";

                cin >> data[i];

        }

}


template <class U>

void UArray<U>::View()         // 배열 data[i]의 값을 출력하는 함수

{

        int i;

        

        for(i = 0; i < size; i++)

        {  

                cout << data[i] << endl;

        }

}

        

main()

{

        UArray<int> obj(5);

        obj.InOut();

       obj.View();

        return 0;

}


  위의 예제는 UArray의 생성자를 통하여 5개의 배열 요소를 포함하는 obj 객체가 만들어진다. 멤버함수 InOut()가 호출되면 다섯 개의 수를 입력받아 화면에 출력하는데 그것은 멤버함수 View()에서 이루어진다.


  이 프로그램의 실행 결과는 다음과 같다.


        Enter a number : 1

        Enter a number : 2

        Enter a number : 3

        Enter a number : 4

        Enter a number : 5

        1

        2

        3

        4

        5


10.3.3 클래스 템플릿의 중첩 정의


  A라는 클래스 템플릿이 있고 B라는 클래스 템플릿이 있다고 하자. 그것은 클래스 템플릿을 이용하여 정의되었다고 하자. 그런데 B라는 클래스 템플릿은 단지 클래스 템플릿 A를 정의하기 위한 목적으로 만들어진 것이라 하자. 그러한  클래스 템플릿 B가 외부에서 정의된다면 다른 목적으로 사용되는 것을 막을 수 없을 것이다. 이런 것을 막기 위하여 클래스 탬플릿 B를 단지 클래스 템플릿 A를 정의하는 목적으로 만들 수 있을까?


  클래스 템플릿 B를 클래스 템플릿 A 내부에 정의하여 클래스 템플릿 B의 참조영역을 클래스 템플릿 A의 정의 부분만으로 한정시키면 어떨까?

  그것에 관한 예제를 아래에서 살펴보자.


<예제 10.16 >

// test4.cpp

#include <iostream.h>

        

template<class T>

class A

{

        template<class U>

        class B

        {

                // class B의 정의 부분

        };

        

                // 그 외 class A의 정의 부분

};


  위의 예제를 보면 그럴듯하게 되어 있지만 이것은 분명히 오류이다.

C++ 컴파일러는 클래스 템플릿을 중첩 정의하는 것을 허용하지 않는다.

이 문제를 해결하려면 어떻게 해야 할까? 바로 구조체를 이용하면 된다. 클래스 템플릿 A의 템플릿 인수를 이용하여 클래스 템플릿 B를 일반 구조체 형식으로 정의할 수 있기 때문이다.


  다음은 클래스 템플릿을 구조체를 이용하여 중첩 정의한 예이다.

<예제 10.17 >

// 구조체를 이용하는 예제 


template <class U>

class queue                     // 클래스 queue 정의

{

        struct queueNode       // queueNode 구조체 선언

        {

                U obj;

                queueNode* next;

                queueNode(const U& u) : obj(u) { next = 0 ; }

        };


        queueNode* head;

        queueNode* tail;

        // 나머지 queue의 정의

        ...

};


  이렇게 정의하면 클래스 queueNode는 클래스 템플릿 queue 내부에 정의되었기 때문에 참조 영역이 클래스 템플릿 queue 내부로 한정되어 외부의 다른 곳에서 참조할 수가 없게 된다.


10.3.4 템플릿과 상속


  템플릿으로 정의되지 않은 클래스는 템플릿으로 정의된 클래스 템플릿을 상속받을 수 있다.


        class A : private stack<int>

                {

                      ...

                }

         

  클래스 A는 클래스 템플릿 stack으로부터 생성된 템플릿 클래스 stack<int>를 상속받았다.  그리고 클래스 템플릿은 다른 클래스 템플릿으로부터 생성될 수 있는 일반 클래스를 상속받을 수 있으며 다음은 이것에 관한 예이다.


        template<class T>

        class A : public virtual stack<T> , A

                {

                        ...

                };


  이렇게 하면 여기서 생성되는 모든 A<T> 템플릿 클래스는 stack<T>와 클래스 A를 상속받는다. 그러나 클래스 템플릿 전체를 상속받을 수는 없다.


  예를 들어 다음과 같은 코드는 오류를 발생시킨다.


class one :

private template<class T> class stack  

        // 오류(클래스 템플릿 전체를 상속 못함)

{

        ...

};


template<class U>

class someTemplate :

private virtual template<class T> class stack

        // 오류 : 클래스 템플릿 전체를 상속할 수 없음

{

        ...

};


  우리가 앞에서 정의한 클래스 템플릿 stack은 실행에서 약간의 문제를 발생시킬 수 있다. stack은 삽입된 자료를 배열로 저장하고 있는데 이 배열의 길이가 고정되어 있다. 따라서 이 배열의 길이를 넘어서면 더 이상의 자료가 삽입되지 않는다. 마찬가지로 비어있는 stack을 삭제하려고 해도 역시 문제가 발생한다.

  여기서 이런 문제들을 stack 클래스로부터 상속받아 실행시의 문제를 처리할 수 있는 파생 클래스 템플릿을 정의해 보도록 하자.

  다음은 이것에 관한 예제이다.


<예제 10.18 >

// fixStack.h

#include <iostream.h>

#include <stdlib.h>

#include  "intstack.h"   // 앞에서 만든 stack의

                      // 클래스 템플릿이 정의된 헤더 파일

template<class T>   

class fixStack : public stack<T>

{

        int stSize;

        public:

        fixStack(int sz = StkSize) : stack<T>(sz) { stSize = sz; }

        void push(T s)

        {

                if(stkPointer >= stkSize)    // 스택이 꽉 참 

                {

                 // 오류 출력

                        cerr << “Stack push error : Stack full\n";

                        exit(-1);

                }

                stack<T>::push(s);

        }

        

        T pop()

        {

                if(stkPointer == 0)

                {

                        // 오류 출력

                        cerr << “Stack Pop Error : Stack Empty.\n";

                        exit(-1);

                }

                return stack<T>::pop();

        }

};


  이제 다시 정의된  클래스 템플릿 fixStack을 이용하여 삽입과 삭제 연산이 이루어지는 프로그램을 만들어 보도록 하자. 정의된 클래스 템플릿은 fixStack.h 파일에 있으므로 #include를 당연히 시켜 주어야 한다.

  다음은 위의 클래스 템플릿 fixStack을 이용한 예제이다.


<예제 10.19 >

#include "fixStack.h"

        

void a()

{

        stack<int> s1(2);  // 길이가 2인 stack

             

        s1.push(1); 

        s1.push(2);        // 이 부분에서 스택이 꽉 차게 된다.

        s1.push(0);        // 오류로 처리하지 않으므로 위험하다.

}

        

void b()

{

        fixStack<int> s2(2);  // 길이가 2인 fixStack

               

        s2.push(4);

        s2.push(5);           // 여기서 스택이 꽉 참

        s2.push(6);           // 오류를 발생시키고 프로그램 종료

}


void c()

{

        stack<int> s3;      // 길이가 stkSize인 Stack

        fixStack<int> s4;   // 길이가 stkSize인 fixStack


        (void) s3.pop();     // 빈 스택이라도 오류 처리 않음

        (void) s4.pop();     // 빈 스택이므로 오류를 발생시키고

                           // 프로그램 종료

}


  s1은 stack<int>형의 객체이므로 스택이 가득 찬 상태에서 삽입 연산이 일어나도 오류가 발생하지 않는데 상당히 위험한 결과를 낳을 수 있다. 마찬가지로 s3도 stack<int> 형의 객체이므로 빈 스택 상에서 삭제 연산을 하면 위험한 결과를 낳을 수 있다. 그러나 s2나 s4는 fixStack<int> 형의 객체이므로 스택이 가득 찬 상태에서 삽입 연산이 일어나거나 빈 스택에서 삭제 연산이 일어나면 오류를 발생시키고 프로그램을 종료하기 때문에 실수를 방지할 수 있다.

  이렇게 클래스 템플릿의 상속은 어떤 클래스를 다른 클래스 템플릿을 기반으로 하여 정의할 수 있도록 해주며 오류나 그 밖의 사항들을 첨가해 주게 된다. 바로 객체 지향 프로그램 기법이 클래스 템플릿에도 적용된다는 것을 알 수 있다.  



<프로그래밍 10.1>


  임의의 기본 자료형의 값을 입력받는 템플릿 클래스를 만드는 프로그램을 작성하시오.


// T는 아직 결정되지 않은 자료형을 뜻한다.

#include <iostream.h>


template <class T>

class TData {                         // TData 형의 템플릿 클래스 선언


private:

        T data;


public:

        TData();                 // 생성자


        void Input();

        void Show();


};


template <class T>

TData<T>::TData()             // 생성자 함수          

{

        cout << "Template class Constructed " << endl;


}


template <class T>

void TData<T>::Input()         // 자료를 입력받는 함수

{

        cout << "Input the data : ";

        cin >> data;

}


template <class T>

void TData<T>::Show()         // 입력된 자료 값을 출력해주는 함수

{

        cout << data << endl;

}


main()

{

        TData<int> a;

        a.Input();


        TData<char> b;

        b.Input();


        TData<double> c;

        c.Input();


        a.Show();

        b.Show();

        c.Show();


        return 0;

}


  이 프로그램의 실행 결과는 다음과 같다.


Template class Constructed

Input the data : 25

Template class Constructed

Input the data : p

Template class Constructed

Input the data : 3.14

25

p

3.14

<프로그래밍 10.2>  

임의의 멱(제곱)을 계산하는 프로그램을 템플릿을 이용하여 작성하시오.

// 두개의 기본 자료형을 사용하는 템플릿

#include <iostream.h>

#include <math.h>


template <class T, class U>    // 클래스 템플릿 some 선언

class some

{


private:

        T a;

        U b;

        double result;


public:

        some(T n, U i);         // 생성자       

        void Calc();

        void Show();

};


template <class T, class U>    // 생성자 함수

some<T,U>::some(T n, U i)

{

        a = n;

        b = i;

}


template <class T, class U>

void some<T,U>::Calc()      // 두 값을 받아서 제곱을 계산하는 함수

{

        result = pow(a,b);

}


template <class T, class U>

void some<T,U>::Show()

{

        cout << result << endl;

}


main()

{

        some<double,double> k(2,3);

        k.Calc();

        k.Show();

        return 0;

}


  이 프로그램의 실행결과는 다음과 같다.


8

<프로그래밍 10.3>


구조체를 이용하여 한 사람의 이름, 나이, 키를 출력하는 프로그램을 작성하시오.


// 구조체를 이용한 템플릿


#include <iostream.h>

#define tab '\t'


struct person                   // 사람의 정보를 담는 구조체 선언

{

        char name[20];

        int  old;

        double tall;

};


// 연산자 중복을 이용한 입출력 처리 부분

ostream& operator << (ostream& out, person& q)

{

        out << q.name << tab;

        out << q.old << tab;

        out << q.tall << endl;

        return out;

};


istream& operator >> (istream& in, person& q)

{

        cout << "Enter name : ";

        in >> q.name;

        cout << "Enter old : ";

        in >> q.old;

        cout << "Enter tall : ";

        in >> q.tall;

        return in;

};


template <class T>

class NoSimple

{


private:

        T d;


public:

        NoSimple();             // 생성자

        void Gather();          // 자료 입력 함수

        void Show();           // 출력 함수

};


template <class T>

NoSimple<T>::NoSimple()       // 생성자 함수

{

        cout << "Template class constructed" << endl;

}


template <class T>

void NoSimple<T>::Gather()     // 자료 입력 함수

{

        cin >> d;

}


template <class T>

void NoSimple<T>::Show()      // 출력 함수

{

        cout << d << endl;

}


main()

{

        NoSimple<person> x;   // 템플릿 개체 선언

        x.Gather();

        x.Show();

        return 0;

}


  이 프로그램의 실행 결과는 다음과 같다.


Template class constructed

Enter name : choi

Enter old : 25

Enter tall : 170

choi   25     170

<연습 문제>


1. 다음 함수 템플릿 선언에서 무엇이 잘못 되었습니까?


        template<class U, int N>

        U reverse(U a[N]);


        extern template<class U>

        U Fun(U a, U b);



2. 다음과 같은 코드가 있다고 하자.


        template <class T>

        T max(T n1, n2)

        {

                return (n1 > n2) ? n1 : n2;

        }


  다음 설명 중 맞는 것은?


        a. n1과 n2는 자료형이다.

        b. 가장 긴 자료형 이름이 반환된다.

        c. 키워드 template은 T에 수 값을 사용할 수 있다는 뜻이다.

        d. T는 자료형을 나타낸다.

        e. 키워드 class는 새 클래스를 정의한다는 뜻이다.


3.  다음과 같이 함수 템플릿과 함수, 그리고 변수가 선언되었다고 가정하고 다음 물음에 답하시오.

        

        int i = 200;

        double j = 30.5;

        char k = 'K';


        template<class T>

        void x(T,T);              // (a)

        void x(double, double);    // (b)

        void x(int, int)            // (c)


  다음의 각 함수 호출들은 (a), (b, (c) 중 어느 것을 호출하게 되는가?


        (1) x(k, k);

        (2) x(s, k);

        (3) x(d, s);


4.  두 개의 변수를 서로 바꾸는 swap에 대한 템플릿 함수를 구현하시오.


5. 클래스 템플릿은 내부에 중첩될 수 없는데 그 이유는 무엇입니까?


6. 다음 프로그램의 결과는 어떻게 나올까? 그리고 왜 그렇게 되는 것인지 설명하시오.


#include <iostream.h>

        

template<class T, int N>

struct  enumVec

{

        void operator() (T a[N])

        {

                cout << "{" << a[0];

                for(int i = 1; i < N; i++)

                {

                        cout << "," << a[i];

                        cout << "}"\n";

                }

        }

};


const int aLen;

const int bLen;


void main()

{

        int a[aLen] = { 1, 2, 3, 4, 5 };

        char b[bLen + 1] = "abcdef";

        enumVec<int, aLen> f;

        enumVec<char, bLen> g;

        f(a);

        g(b);

}


'C C++ - STL' 카테고리의 다른 글

STL - 8 Vector 라이브러리  (0) 2014.02.13
STL - 7 Sequence Container  (0) 2014.02.13
STL - 6 알고리즘  (0) 2014.02.13
STL - 5 Text Picture  (0) 2014.02.13
STL - 4 Iterator 반복자  (0) 2014.02.13