16장 탬플릿

2023. 11. 30. 13:57프로그래밍 공부/OOP

16.1 함수 템플릿


함수 템플릿 구문

템플릿 접두사

디스플레이 16.1은 함수 swapValues에 대한 C++ 템플릿을 보여준다.

이 함수 템플릿(function template)을 사용하면 두 변수의 형식이 동일한 경우 어떤 형식이든 값을 교환할 수 있다.

정의와 함수 선언은 다음 줄로 시작된다 
template<class T>
이것을 흔히 템플릿 접두사(template prefix)라고 부른다.

컴파일러에게 다음에 나오는 정의나 함수 선언이 템플릿(template)이고

T가 형식 매개변수(type parameter)인 것을 말해준다.

이런 맥락에서 class라는 단어는 실제로 type을 의미한다.

 

템플릿은 함수 이름을 오버로딩한다 
앞으로 보게 되겠지만, 형식 매개변수 T는 형식이 클래스이든 아니든 어떤 유형으로 대체될 수 있다.

형식 매개변수 T는 함수 정의 본문 내에서 다른 유형과 마찬가지로 사용된다.
함수 템플릿 정의는 사실상 많은 함수 정의 모음이다.

디스플레이 16.1에 표시된 swapValues에 대한 함수 템플릿의 경우 가능한 각 유형 이름에 대해 사실상 하나의 함수 정의가 있다.

이들 정의는 각각 형식 매개 변수 T를 유형 이름으로 대체하여 얻어진다.

예를 들어, 다음에 표시된 함수 정의는 T를 double형 이름으로 대체하여 얻어진다:

void swapValues( double& variable1, double& variable2)
{
    double temp;
    temp = variable1;
    variable1 = variable2;
    variable2 = temp;
}

 

swapValues에 대한 또 다른 정의는 함수 템플릿의 형식 매개 변수 T를 int형 이름으로 대체함으로써 얻어진다.

또 다른 정의는 형식 매개 변수 T를 char형으로 대체함으로써 얻어진다.

디스플레이 16.1의 함수 템플릿 하나는 가능한 모든 형식에 대해 함수 이름 swapValues를 오버로딩하여 함수 정의가 조금씩 다르다.


컴파일러는 말 그대로 함수 이름 swapValues에 대해 가능한 모든 형식의 정의를 생성하지는 않지만, 

모든 형식의 함수 정의를 생성한 것처럼 정확히 동작한다.

템플릿을 사용하는 각 형식에 대해 별도의 정의가 생성되지만 사용하지 않는 형식의 정의는 생성되지 않는다.

템플릿을 사용하는 횟수에 관계없이 단일 형식에 대해 하나의 정의만 생성된다.

디스플레이 16.1에서 함수 swapValues는 두 번 호출된다.

한 번은 int 형의 인수이고, 다른 한 번은 char 형의 인수이다.

디스플레이 16.1에서 다음과 같은 함수 호출을 고려해 보자:

swapValues(integer1, integer2);

 

디스플레이 16.1 함수 템플릿

//Program to demonstrate a function template.
#include <iostream>
using std::cout;
using std::endl;

//Interchanges the values of variable1 and variable2.
//The assignment operator must work for the type T.
template<class T>
void swapValues(T& variable1, T& variable2)
{
	T temp;
	temp = variable1;
	variable1 = variable2;
	variable2 = temp;
}

int main( )
{
	int integer1 = 1, integer2 = 2;
	cout << "Original integer values are "
	     << integer1 << " " << integer2 << endl;
	swapValues(integer1, integer2);
	cout << "Swapped integer values are "
	     << integer1 << " " << integer2 << endl;
         
	char symbol1 = 'A', symbol2 = 'B';
	cout << "Original character values are: "
	     << symbol1 << " " << symbol2 << endl;
	swapValues(symbol1, symbol2);
	cout << "Swapped character values are: "
	     << symbol1 << " " << symbol2 << endl;
	return 0;
}

많은 컴파일러들이 여전히 템플릿에 문제가 있다.
템플릿이 가장 광범위한 선택 항목에서 작동하는지 확인하려면
컴파일러, 템플릿 정의를 사용되는 파일과 동일한 파일에 배치하고

템플릿 정의를 템플릿의 모든 사용보다 먼저 사용하도록 한다.

 

샘플 대화 상자

Original integer values are: 1 2
Swapped integer values are: 2 1
Original character values are: A B
Swapped character values are: B A

 

C++ 컴파일러가 이 함수 호출에 도달하면

인수의 종류(이 경우 int)를 알아채고 템플릿을 사용하여

형식 매개변수 T가 int형 이름으로 대체된 함수 정의를 생성한다.

마찬가지로 컴파일러가 함수 호출을 볼 때
swapValues(symbol1, symbol2);
인수의 유형(이 경우 char)을 인식한 다음 템플릿을 사용하여 

char형 이름으로 대체된 형식 매개 변수 T를 사용하여 함수 정의를 생성한다.

 

함수 템플릿 호출하기
함수 템플릿으로 정의된 함수를 호출할 때는 특별한 작업을 수행할 필요가 없다. 

다른 함수와 마찬가지로 호출하라.
컴파일러는 함수 템플릿에서 함수 정의를 생성하는 모든 작업을 수행한다.
함수 템플릿은 일반 함수와 마찬가지로 함수 선언과 정의를 가질 수 있다.

함수 템플릿에 대한 함수 선언과 정의를 일반 함수에 대한 함수 선언과 정의를 배치하는 것과 동일한 위치에 배치할 수 있다.

그러나 대부분의 컴파일러에서는

아직 템플릿 정의와 템플릿 함수 선언에 대한 별도의 컴파일이 구현되지 않으므로

디스플레이 16.1에서와 같이

템플릿 함수를 호출하는 파일에 템플릿 함수 정의를 배치하는 것이 가장 안전하다.

사실 대부분의 컴파일러는 템플릿 함수의 첫 번째 호출 전에 템플릿 함수 정의가 나타나도록 요구한다.

템플릿 함수를 호출하기 전에 템플릿 함수 정의가 들어 있는 파일을 #include시켜도 된다.

특정 컴파일러의 동작이 다를 수 있으므로 자세한 내용은 지역 전문가에게 문의해야 한다.


디스플레이 16.1의 함수 템플릿에서 우리는 T자를 형식에 대한 매개 변수로 사용했다.

이것은 전통적인 것이지만 C++ 언어에서는 필요하지 않다.

형식 매개 변수는 (키워드 이외의) 임의의 식별자일 수 있다.

T는 형식 매개 변수에 대한 좋은 이름이지만 다른 이름도 사용할 수 있다.

 

하나 이상의 형식 매개변수 
하나 이상의 형식 매개변수를 가지는 함수 템플릿을 가질 수 있다.
예시)

T1 및 T2라는 두 가지 형식의 매개 변수가 있는 함수 템플릿은 다음과 같이 시작된다:
template<class T1, class T2>
그러나 대부분의 함수 템플릿은 하나의 유형 매개 변수만 필요하다.

사용되지 않은 템플릿 매개 변수를 가질 수 없다.

즉, 템플릿 함수에서 각 템플릿 매개 변수를 사용해야 한다.


함정: 컴파일러 컴파일

많은 컴파일러들이 템플릿들을 따로 컴파일하는 것을 허용하지 않으므로 

템플릿 정의를 이를 사용하는 코드에 포함시켜야 할 수도 있다.

평소처럼 적어도 함수 선언은 함수 템플릿을 사용하기 전에 선행되어야 한다.


일부 C++ 컴파일러는 템플릿을 사용하는 데 추가적인 특별 요구 사항이 있다.

템플릿을 컴파일하는 데 문제가 있다면 설명서를 확인하거나 지역 전문가에게 확인하자.

특별한 옵션을 설정하거나 템플릿 정의 및 파일의 다른 항목을 순서로 지정하는 방법을 재정렬해야 할 수도 있다.

가장 광범위한 컴파일러와 함께 작동하는 것으로 보이는 템플릿 프로그램 레이아웃은 다음과 같다.

템플릿 정의를 사용되는 동일한 파일에 넣고 템플릿 정의가 템플릿의 모든 사용(모든 호출)보다 먼저 수행되도록 하자.

응용 프로그램과 별도의 파일에 함수 템플릿 정의를 배치하려면

응용 프로그램 파일에 함수 템플릿 정의가 있는 파일을 #include할 수 있다.

 

알고리즘 추상화
swapValues 함수에 대한 논의에서 보았듯이, 

어떤 종류의 변수에도 적용되는 두 변수의 값을 바꾸는 매우 일반적인 알고리즘이 있다.

함수 템플릿을 사용하여 이보다 일반적인 알고리즘을 C++로 표현할 수 있었다.

이것은 알고리즘 추상화의 아주 간단한 예이다.

알고리즘 추상화: 부수적인 세부 사항을 무시하고 알고리즘의 실질적인 부분에 집중할 수 있도록 매우 일반적인 방식으로 알고리즘을 표현하는 것

함수 템플릿은 알고리즘 추상화를 지원하는 C++의 한 가지 기능이다.


팁: 템플릿을 정의하는 방법

디스플레이 16.1에서 함수 템플릿을 정의할 때, 먼저 int 형식의 요소들을 정렬하는 함수부터 시작했다.

그런 다음 배열의 기본 형식을 형식 매개 변수 T로 대체하여 템플릿을 만들었다.

이것은 템플릿을 작성하는 데 좋은 일반적인 전략이다.

함수 템플릿을 작성하려면 먼저 템플릿이 아닌 그냥 일반 함수인 버전을 작성해야 한다.

그런 다음 일반 함수를 완전히 디버깅하고, 마지막으로 일부 형식 이름을 형식 매개 변수로 대체하여 일반 함수를 템플릿으로 변환한다.

이 방법의 장점은 두 가지이다.

1. 일반 함수를 정의할 때 훨씬 더 구체적인 경우를 처리하므로 문제가 시각화되기 쉽다.

2, 각 단계에서 확인해야 할 세부 사항이 줄어들고 알고리즘 자체를 걱정할 때 템플릿 구문 규칙에 신경 쓸 필요가 없다.

 

함수 템플릿
함수 정의와 함수 템플릿의 함수 선언은 각각 다음과 같이 접두사로 붙는다:
template<class Type_Parameter>
함수 선언(사용되는 경우) 및 정의는

Type_Parameter를 유형 대신 사용할 수 있다는 점을 제외하고는

일반 함수 선언 및 정의와 동일하다.

 

예시)

함수 템플릿에 대한 함수 선언은 다음과 같다:
template<class T>
void showStuff( int stuff1, T stuff2, T stuff3);
이 함수 템플릿의 정의는 다음과 같다:
template<class T>
void showStuff( int stuff1, T stuff2, T stuff3)
{
cout << stuff1 << endl
        << stuff2 << endl
        << stuff3 << endl;
}

 

이 예제에서 주어진 함수 템플릿은 가능한 각 형식 이름에 대해 하나의 함수 선언과 하나의 함수 정의를 갖는 것과 같다.

형식 이름은 형식 매개 변수(앞의 예제에서는 T)로 대체된다.

예를 들어 다음 함수 호출을 생각해 보자:
showStuff(2, 3.3, 4.4);
이 함수 호출이 실행되면 컴파일러는 T를 double 형식 이름으로 바꾸어 얻은 함수 정의를 사용한다.

템플릿을 사용하는 각 다른 유형에 대해서는 별도의 정의가 생성되지만 사용하지 않는 유형에 대해서는 생성되지 않는다.

템플릿을 사용하는 횟수에 상관없이 특정 유형에 대해서는 하나의 정의만 생성된다.


예: 일반 정렬 함수

제5장에서는 int 형식의 값들의 배열을 정렬하는 선택 정렬 알고리즘을 제시하였고, 

이 알고리즘은 C++ 코드로 구현되어 있으며, 이 함수 sort는 디스플레이 5.8에 제시되어 있다.
다음에서는 이 함수 sort의 정의를 반복한다:

void sort( int a[], int numberUsed)
{
    int indexOfNextSmallest;
    for ( int index = 0; index < numberUsed - 1; index++)
    {//Place the correct value in a[index]:
        indexOfNextSmallest = indexOfSmallest(a, index, numberUsed);
        swapValues(a[index], a[indexOfNextSmallest]);

        //a[0] <= a[1] <=...<= a[index] are the smallest of the
        // original array elements. The rest of the elements
        //are in the remaining positions.

    }
}

 

앞에서 정의한 함수 sort를 공부하면 배열의 기본형은 어떤 의미 있는 방식으로도 사용되지 않는다는 것을 알 수 있다.

함수 헤더에 있는 배열의 기본형을 double형으로 바꾸면 우리는 double형 값의 배열에 적용되는 정렬 함수를 얻을 수 있다.

물론 우리는 도움 함수도 double형 원소의 배열에 적용되도록 조정해야 한다.

 

함수 정렬의 본문 안에서 호출되는 도움 함수들을 살펴보자. 

두 도움 함수는 swapValues와 indexOfSmallest이다.

우리는 swapValues가 할당 연산자가 작동하는 모든 형식의 변수에 적용될 수 있다는 것을 보았다.

indexOfSmallest가 정렬되는 배열의 기본 형식에 중요한 방식으로 의존하는지 알아보겠다.

indexOfSmallest의 정의는 다음과 같이 반복되므로 자세한 내용을 연구할 수 있다.

int indexOfSmallest( const int a[], int startIndex, int numberUsed)
{
    int min = a[startIndex],
          indexOfMin = startIndex;
    for ( int index = startIndex + 1; index < numberUsed; index++)
        if (a[index] < min)
        {
        min = a[index];
        indexOfMin = index;
        
        //min is the smallest of a[startIndex] through
        //a[index]
        
        }
    return indexOfMin;
}

함수 indexOfSmallest 또한 배열의 기본 형식에 크게 의존하지 않는다.

만약 int형의 강조된 두 개의 인스턴스를 double형으로 바꾼다면,

함수 indexOfSmallest를 기본 형식이 double인 배열에 적용되도록 바꾸었을 것이다.
기본 형식이 double인 배열을 정렬하는 데 사용할 수 있도록 함수 sort를 변경하려면

int 형식 이름의 인스턴스를 몇 개만 double형으로 바꾸면 된다.

게다가 double형은 특별한 것이 없다.


다른 많은 형식들도 비슷하게 대체할 수 있다.

형식에 대해 알아야 할 유일한 것은 할당 연산자와 연산자 <가 해당 형식에 대해 정의되어 있다는 것이다.

이것이 함수 템플릿을 위한 완벽한 상황이다.

(함수 sort과 indexOfSmallest에서) int 형식 이름의 몇 가지 인스턴스를 형식 매개 변수로 대체하면

해당 형식의 값을 할당 연산자와 할당하고 <연산자를 사용하여 비교할 수 있다면

함수 sort는 모든 형식의 값 배열을 정렬할 수 있다.

디스플레이 16.2에서 우리는 이러한 함수 템플릿을 작성했다.


디스플레이 16.2의 함수 템플릿 sort는 숫자가 아닌 값의 배열로 사용할 수 있다.

시연 프로그램에서는 함수 템플릿 sort를 호출하여 문자 배열을 정렬한다.

연산자를 사용하여 문자를 비교할 수 있는데,

이 연산자는 ASCII 번호의 순서에 따라 문자를 비교한다(부록 3 참조).

 

따라서 연산자 <는

case1) 두 개의 대문자에 적용할 때

            → 첫 번째 문자가 알파벳 순서로 두 번째 앞에 오는지 확인하기 위해 테스트한다.

case2) 두 개의 소문자에 적용할 때

            → <는 첫 번째 문자가 알파벳 순서로 두 번째 앞에 오는지 확인하기 위해 테스트한다.

case3) 대문자와 소문자를 혼합할 때

            → 디스플레이 16.2는 대문자만 처리한다.

 

이 프로그램에서 대문자 배열은 함수 템플릿 sort에 대한 호출과 함께 알파벳 순서로 정렬된다.
(< 연산자를 클래스의 개체에 적용하기 위해 오버로딩하는 경우 함수 템플릿 sort는 정의한 클래스의 객체 배열까지 정렬한다.)

 

우리의 일반 정렬 함수는 정렬 함수의 정의를 sort.cpp 파일에 위치시킴으로써

정렬 함수의 선언에서 구현을 분리했다(디스플레이 16.3).

그러나 대부분의 컴파일러는 일반적인 의미에서 템플릿을 별도로 컴파일하는 것을 허용하지 않는다.

그래서 프로그래머의 관점에서 구현을 분리했지만 컴파일러의 관점에서 보면 모든 것이 하나의 파일에 있는 것처럼 보인다.

sort.cpp 파일은 우리의 main 파일에 #include되므로 마치 모든 것이 하나의 파일에 있는 것과 같다.

sort.cpp에 대한 include 지시문은 템플릿에 의해 정의된 함수를 호출하기 전에 배치된다.

대부분의 컴파일러에게 이것이 템플릿을 작동시키는 유일한 방법이다.

 

디스플레이 16.2 일반 정렬 함수

//Demonstrates a template function that implements
//a generic version of the selection sort algorithm.
#include <iostream>
using std::cout;
using std::endl;

template<class T>
void sort(T a[], int numberUsed);
//Precondition: numberUsed <= declared size of the array a.
//The array elements a[0] through a[numberUsed - 1] have values.
//The assignment and < operator work for values of type T.
//Postcondition: The values of a[0] through a[numberUsed - 1] have
//been rearranged so that a[0] <= a[1] <=... <= a[numberUsed - 1].

template<class T>
void swapValues(T& variable1, T& variable2);
//Interchanges the values of variable1 and variable2.
//The assignment operator must work correctly for the type T.

template<class T>
int indexOfSmallest( const T a[], int startIndex, int numberUsed);
//Precondition: 0 <= startIndex < numberUsed. Array elements have
//values.
//The assignment and < operator work for values of type T.
//Returns the index i such that a[i] is the smallest of the values
//a[startIndex], a[startIndex + 1],..., a[numberUsed - 1].

#include "sort.cpp"

int main( )
{
	int i;
	int a[10] = {9, 8, 7, 6, 5, 1, 2, 3, 0, 4};
	cout << "Unsorted integers:\n";
	for (i = 0; i < 10; i++)
		cout << a[i] << " ";
	cout << endl;
	sort(a, 10);
	cout << "In sorted order the integers are:\n";
	for (i = 0; i < 10; i++)
		cout << a[i] << " ";
	cout << endl;
    
	double b[5] = {5.5, 4.4, 1.1, 3.3, 2.2};
	cout << "Unsorted doubles:\n";
	for (i = 0; i < 5; i++)
		cout << b[i] << " ";
	cout << endl;
	sort(b, 5);
	cout << "In sorted order the doubles are:\n";
	for (i = 0; i < 5; i++)
		cout << b[i] << " ";
	cout << endl;
    
	char c[7] = {'G', 'E', 'N', 'E', 'R', 'I', 'C'};
	cout << "Unsorted characters:\n";
	for (i = 0; i < 7; i++)
		cout << c[i] << " ";
	cout << endl;
	sort(c, 7);
	cout << "In sorted order the characters are:\n";
	for (i = 0; i < 7; i++)
		cout << c[i] << " ";
	cout << endl;
    
	return 0;
}

#include "sort.cpp"

이것은 이 파일에 함수 템플릿 정의를 이 위치에 배치하는 것과 같다.

 

샘플 대화 상자

Unsorted integers:
9 8 7 6 5 1 2 3 0 4
In sorted order the integers are:
0 1 2 3 4 5 6 7 8 9
Unsorted doubles:
5.5 4.4 1.1 3.3 2.2
In sorted order the doubles are:
1.1 2.2 3.3 4.4 5.5
Unsorted characters:
G E N E R I C
In sorted order the characters are:
C E E G I N R

 

Display 16.3 일반 정렬 함수의 구현

// This is the file sort.cpp.

template<class T>
void sort(T a[], int numberUsed)
{
	int indexOfNextSmallest;
	for ( int index = 0; index < numberUsed - 1; index++)
	{ //Place the correct value in a[index]:
		indexOfNextSmallest =
		     indexOfSmallest(a, index, numberUsed);
		swapValues(a[index], a[indexOfNextSmallest]);
		//a[0] <= a[1] <=...<= a[index] are the smallest of the original
		//array elements. The rest of the elements are in the remaining
		//positions.
	}
}

template<class T>
void swapValues(T& variable1, T& variable2)
<The rest of the definition of swapValues is given in Display 16.1 .>

template<class T>
int indexOfSmallest( const T a[ ], int startIndex, int numberUsed)
{
	T min = a[startIndex];
	int indexOfMin = startIndex;
    
	for ( int index = startIndex + 1; index < numberUsed; index++)
		if (a[index] < min)
		{
			min = a[index];
			indexOfMin = index;
			//min is the smallest of a[startIndex] through a[index] .
		}
	return indexOfMin;
}

T min = a[startIndex];

형식 매개변수는 함수 정의 본문에서 사용할 수 있다.

 

함정: 부적절한 형식의 템플릿 사용

함수 정의의 코드가 의미가 있는 모든 유형의 템플릿 함수를 사용할 수 있다.

그러나 템플릿 함수의 모든 코드는 명확해야 하며 적절한 방식으로 작동해야 한다.

예를 들어 swapValues 템플릿(디스플레이 16.1)을 형식 매개 변수로 지정한 경우

할당 연산자가 전혀 작동하지 않거나 "올바르게" 작동하지 않는 유형으로 대체하여 사용할 수 없다.
좀 더 구체적인 예를 들어, 프로그램이 디스플레이 16.1과 같이 템플릿 함수 swapValues를 정의한다고 가정한다.

프로그램에 다음을 추가할 수 없다:

int a[10], b[10];
<some code to fill arrays>
swapValues(a, b);

배열 형식에서는 할당이 작동하지 않으므로 이 코드가 작동하지 않는다.

 

16.2 클래스 템플릿


클래스 템플릿 구문

클래스 템플릿의 구문 세부 정보는 기본적으로 함수 템플릿의 구문 세부 정보와 동일하다.

템플릿 정의 앞에는 다음이 있다:
template<class T>

 

형식 매개변수 T

형식 매개변수 T는 다른 Type과 마찬가지로 클래스 정의에서 사용된다.

형식 매개변수는 함수 템플릿과 마찬가지로 임의의 형식이 될 수 있는 형식을 나타낸다.

T를 사용하는 것이 전통적이지만 함수 템플릿과 마찬가지로 T 대신 임의의 (키워드가 아닌) 식별자를 사용할 수 있다.

 

디스플레이 16.4는 클래스 템플릿의 예를 보여줍니다. 

이 클래스의 객체는 T 형식의 값 쌍을 포함한다.

T가 int이면 객체 값은 정수의 쌍, T가 char이면 객체 값은 문자의 쌍 등이다.

 

객체 선언하기

클래스 템플릿이 정의되면 이 클래스의 객체를 선언할 수 있다.

선언은 T에 어떤 형식을 입력할지 지정해야 한다.

 

예시)

다음은 객체 score를 선언하여 정수의 쌍을 기록할 수 있고

객체 seats를 선언하여 문자의 쌍을 기록할 수 있다:
Pair<int> score;
Pair<char> seats;
그리고 나서 이 물체들은 다른 물체들처럼 사용된다.

예시)

다음은 첫 번째 팀에게는 3점, 두 번째 팀에게는 0점으로 점수를 설정한다:
score.setFirst(3);
score.setSecond(0);

 

 

16.4 클래스 템플릿 정의

//Class for a pair of values of type T:
template<class T>
class Pair
{
public:
	Pair( );
	Pair(T firstValue, T secondValue);
	void setFirst(T newValue);
	void setSecond(T newValue);
	T getFirst( ) const;
	T getSecond( ) const;
private:
	T first;
	T second;
};

template<class T>
Pair<T>::Pair(T firstValue, T secondValue)
{
	first = firstValue;
	second = secondValue;
}

template<class T>
void Pair<T>::setFirst(T newValue)
{
	first = newValue;
}

template<class T>
T Pair<T>::getFirst( ) const
{
	return first;
}

여기에 모든 멤버 함수가 표시된 것은 아니다.

 

멤버 함수 정의하기

클래스 템플릿의 멤버 함수는 일반 클래스의 멤버 함수와 동일한 방식으로 정의된다.

유일한 차이점은 멤버 함수 정의가 템플릿 자체라는 것이다.

예를 들어, 디스플레이 16.4는 멤버 함수 setFirst와 getFirst에 대한 적절한 정의와

템플릿 클래스 Pair에 대한 두 인수를 가진 생성자에 대한 정의를 보여준다.

범위 지정 연산자 앞에 있는 클래스 이름은 Pair<T>이고 단순 Pair가 아니다.

그러나 범위 지정 연산자 뒤에 있는 생성자 이름은 <T>가 없는 단순한 이름 Pair이다.

 

매개 변수로써 클래스 템플릿

클래스 템플릿의 이름은 함수 매개 변수의 형식으로 사용될 수 있다.
예시)

정수 쌍에 대한 매개 변수가 있는 함수에 대한 가능한 함수 선언은 다음과 같다:
int addUp( const Pair< int>& thePair);
//쌍에 있는 두 정수의 합을 반환한다.
형식 매개 변수 T에 입력할 형식(이 경우 int)을 지정했다.
함수 템플릿 내에서 클래스 템플릿을 사용할 수도 있다.

예를 들어, 이전 코드에서 주어진 특수 함수 addUp을 정의하는 대신

함수 템플릿을 다음과 같이 정의하여 모든 종류의 숫자에 함수를 적용할 수 있다:

template<class T>
T addUp( const Pair<T>& thePair);
//Precondition: The operator + is defined for values of type T.
//Returns the sum of the two values in thePair.

 

형식 매개변수의 제한 사항

거의 모든 템플릿 클래스 정의에는 형식 매개 변수(또는 매개 변수)를 합리적으로 대체할 수 있는 형식에 대한 몇 가지 제한이 있다.

Pair와 같은 간단한 템플릿 클래스도 절대적으로 모든 형식 T와 잘 작동하지 않는다.

할당 연산자가 멤버 함수 정의에 사용되고 T 형식의 호출 단위 매개 변수를 가진 멤버 함수가 있기 때문에

할당 연산자와 복사 생성자가 T 형식에 대해 잘 작동하지 않는 한 형식 Pair는 잘 작동하지 않을 것이다.

T가 포인터와 동적 변수를 포함하는 경우 T에도 적합한 소멸자가 있어야 한다.

그러나 이러한 요구 사항은 잘 작동하는 클래스 형식 T가 가질 것으로 예상할 수 있는 요구 사항이다.

따라서 이러한 요구 사항은 최소화된다.

다른 템플릿 클래스의 경우 형식 매개 변수를 대체할 수 있는 형식에 대한 요구 사항이 더 제한적일 수 있다.

 

클래스 템플릿 구문
클래스 템플릿 정의 및 해당 멤버 함수의 정의 앞에는 다음이 있다:
template<class Type_Parameter>
클래스 및 멤버 함수 정의는 Type_Parameter를 형식 대신 사용할 수 있다는 점을 제외하고는 일반 클래스와 동일하다.
예를 들어 클래스 템플릿 정의의 시작은 다음과 같다:
template<class T>
class Pair
{
public:
    Pair( );
    Pair(T firstValue, T secondValue);
    ...
그런 다음 멤버 함수와 오버로딩 연산자를 함수 템플릿으로 정의한다.

예를 들어, 이전 샘플 클래스 템플릿에 대한 두 인수 생성자의 정의는 다음과 같이 시작된다:
template<class T>
Pair<T>::Pair(T firstValue, T secondValue)
{
    ...
다음 예제와 같이 클래스 이름에 형식 인수를 지정하여 클래스 템플릿을 지정할 수 있다:
Pair<int>
그러면 Pair<int>와 같은 특수 클래스 이름을 다른 클래스 이름처럼 사용할 수 있다.

객체를 선언하거나 공식 매개 변수의 형식을 지정하는 데 사용할 수 있다.

 

형식 정의
Pair<int>와 같이 특수화된 클래스 템플릿 이름과 동일한 의미를 갖는 새 클래스 형식 이름을 정의할 수 있다.

이러한 정의된 클래스 형식 이름의 구문은 다음과 같다:
typedef Class_Name<Type_Argument> New_Type_Name;
예를 들면,
typedef Pair< int> PairOfInt;
그런 다음 PairOfInt라는 형식 이름을 사용하여 다음 예제와 같이 형식 Pair<int>의 개체를 선언할 수 있다:
PairOfInt pair1, pair2;
형식 매개 변수의 형식을 지정하는 데 PairOfInt 형식 이름을 사용하거나 형식 이름이 허용되는 다른 장소에서도 사용할 수 있다.

 

예: 배열 템플릿 클래스

10장에서는 double형의 부분적으로 채워진 배열에 대한 클래스를 정의했다(디스플레이 10.10 및 10.11).

이 예제에서는 모든 형식의 값의 부분적으로 채워진 배열에 대한 템플릿 클래스로 정의를 변환한다.

템플릿 클래스 PFArray는 기본 배열의 형식에 대한 형식 매개 변수 T를 갖는다.


변환은 일상적이다.

우리는 단지 double(배열의 기본 형식으로 발생할 때)을 형식 매개변수로 대체하고

클래스 정의와 멤버 함수 정의를 모두 템플릿 형식으로 변환한다.

템플릿 클래스 정의는 디스플레이 16.5에 제공된다.

멤버 함수 템플릿 정의는 디스플레이 16.6에 제공된다.


템플릿 정의를 네임스페이스에 배치했다.

네임스페이스는 템플릿과 함께 사용되는 것과 같은 방식으로 템플릿과 함께 사용된다.


예제 응용 프로그램은 디스플레이 16.7에 나와 있다.

클래스 템플릿 인터페이스, 구현 및 응용 프로그램을 세 개의 파일로 분리했다.

유감스럽게도 이 파일들은 기존의 별도 컴파일 방법에서는 사용할 수 없다.

대부분의 컴파일러는 아직 이러한 별도의 컴파일을 제공하지 않는다.

그래서 우리는 인터페이스 파일과 구현 파일을 응용 프로그램 파일에 #include함으로써 최선을 다한다.

컴파일러에게 모든 것이 하나의 파일에 있는 것처럼 보이게 만든다.

 

디스플레이 16.5 PFArray 템플릿 클래스 인터페이스

//This is the header file pfarray.h. This is the interface for the class
//PFArray. Objects of this type are partially filled arrays with base
//type T.
#ifndef PFARRAY_H
#define PFARRAY_H

namespace PFArraySavitch
{
	template<class T>
	class PFArray
	{
	public:
		PFArray( ); //Initializes with a capacity of 50.
        
		PFArray( int capacityValue);
        
		PFArray( const PFArray<T>& pfaObject);
        
		void addElement(const T& element);
		//Precondition: The array is not full.
		//Postcondition: The element has been added.
        
		bool full( ) const; //Returns true if the array is full;
		                    //false, otherwise.
                            
		int getCapacity( ) const;
        
		int getNumberUsed( ) const;
        
		void emptyArray( );
		//Resets the number used to zero, effectively emptying the
		//array.

		T& operator[]( int index);
		//Read and change access to elements 0 through numberUsed - 1.
        
		PFArray<T>& operator =( const PFArray<T>& rightSide);
        
		virtual ~PFArray( );
	private:
		T *a; //for an array of T.
		int capacity; //for the size of the array.
		int used; //for the number of array positions currently in use.
	};
} // PFArraySavitch
#endif //PFARRAY_H

 

디스플레이 16.6 PFArray 템플릿 클래스 구현

//This is the implementation file pfarray.cpp.
//This is the implementation of the template class PFArray.
//The interface for the template class PFArray is in the file pfarray.h.
#include "pfarray.h"
#include <iostream>
using std::cout;

namespace PFArraySavitch
{
	template < class T>
	PFArray<T>::PFArray( ) :capacity(50), used(0)
	{
		a = new T[capacity];
	}
    
	template<class T>
	PFArray<T>::PFArray( int size) :capacity(size), used(0)
	{
		a = new T[capacity];
	}
    
	template<class T>
	PFArray<T>::PFArray( const PFArray<T>& pfaObject)
	       :capacity(pfaObject.getCapacity( )),
           used(pfaObject.getNumberUsed( ))
	{
		a = new T[capacity];
		for ( int i = 0; i < used; i++)
			a[i] = pfaObject.a[i];
	}

	template<class T>
	void PFArray<T>::addElement( const T& element)
	{
		if (used >= capacity)
		{
			cout << "Attempt to exceed capacity in PFArray.\n";
			exit(0);
		}
		a[used] = element;
		used++;
	}

	template<class T>
	bool PFArray<T>::full( ) const
	{
		return (capacity == used);
	}
    
	template<class T>
	int PFArray<T>::getCapacity( ) const
	{
		return capacity;
	}
    
	template<class T>
	int PFArray<T>::getNumberUsed( ) const
	{
		return used;
	}
    
	template<class T>
	void PFArray<T>::emptyArray( )
	{
		used = 0;
	}
    
	template<class T>
	T& PFArray<T>::operator[]( int index)
	{
		if (index >= used)
		{
			cout << "Illegal index in PFArray.\n";
			exit(0);
		}
		return a[index];
	}
    
	template<class T>
	PFArray<T>& PFArray<T>::operator =( const PFArray<T>& rightSide)
	{
		if (capacity != rightSide.capacity)
		{
			delete [] a;
			a = new T[rightSide.capacity];
		}
        
		capacity = rightSide.capacity;
		used = rightSide.used;
		for ( int i = 0; i < used; i++)
			a[i] = rightSide.a[i];
            
		return * this;
	}
    
	template<class T>
	PFArray<T>::~PFArray( )
	{
		delete [] a;
	}
} // PFArraySavitch

template < class T>
PFArray<T>::PFArray( ) :capacity(50), used(0)

T는 범위 지정 연산자 앞에 사용되지만 생성자 이름에는 T가 사용되지 않다.

 

디스플레이 16.7 템플릿 클래스 PFArray 시연 프로그램

//Program to demonstrate the template class PFArray.
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;

#include "pfarray.h"
#include "pfarray.cpp"
using PFArraySavitch::PFArray;

int main( )
{
	PFArray< int> a(10);
    
	cout << "Enter up to 10 nonnegative integers.\n";
	cout << "Place a negative number at the end.\n";    
	int next;
	cin >> next;
	while ((next >= 0) && (!a.full( )))
	{
		a.addElement(next);
		cin >> next;
	}
	if (next >= 0)
	{
		cout << "Could not read all numbers.\n";
		//Clear the unread input:
		while (next >= 0)
			cin >> next;
	}
    
	cout << "You entered the following:\n ";
	int index;
	int count = a.getNumberUsed( );
	for (index = 0; index < count; index++)
		cout << a[index] << " ";
	cout << endl;
	PFArray<string> b(3);
    
	cout << "Enter three words:\n";
	string nextWord;
	for (index = 0; index < 3; index++)
	{
		cin >> nextWord;
		b.addElement(nextWord);
	}
    
	cout << "You wrote the following:\n";
	count = b.getNumberUsed( );
	for (index = 0; index < count; index++)
		cout << b[index] << " ";
	cout << endl;
	cout << "I hope you really mean it.\n";
    
	return 0;
}

 

샘플 대화 상자

Enter up to 10 nonnegative integers.
Place a negative number at the end.
1 2 3 4 5 -1
You entered the following:
1 2 3 4 5
Enter three words:
I love you
You wrote the following:
I love you
I hope you really mean it

 

친구 함수
친구 함수는 템플릿 클래스와 함께 사용되는 것과 같은 방식으로 일반 클래스와 함께 사용된다.

다른 점은 적절한 경우 형식 매개변수를 포함해야 한다는 것이다.

 

vector 및 basic_string 템플릿

vector

아직 그렇게 하지 않았다면, 템플릿 클래스 vector를 다루는 7장의 7.3절을 읽어보기에 좋은 시간이 될 것이다.

 

basic_string

basic_string 템플릿 클래스: 모든 유형의 요소의 문자열을 처리할 수 있는 템플릿 클래스

클래스 basic_string<char>: 문자의 문자열에 대한 클래스

클래스 basic_string<double>: double 형식의 문자열에 대한 클래스

클래스 basic_string<YourClass>: 클래스 YourClass의 객체의 문자열에 대한 클래스
당신은 이미 basic_string 템플릿 클래스의 특수한 경우를 사용하고 있다. 

우리가 사용해온 꾸밈없는 이름 string은 basic_string<char> 클래스의 대체 이름이다. 

클래스 string에 대해 배운 모든 멤버 함수는 템플릿 클래스 basic_string<T>에 대해 적용되고 비슷하게 동작한다.


템플릿 클래스 basic_string은 헤더 파일 <string>으로 라이브러리에서 정의되며 정의는 std 네임스페이스에 배치된다. 

따라서 클래스 basic_string을 사용할 때는 파일의 시작 부분에 다음과 같은 것이 필요하다:

#include <string>
using namespace std;
or
#include <string>
using std::basic_string;
using std::string; //Only if you use the name string by itself

 

16.3 템플릿과 상속

 

예: 백업이 있는 부분적으로 채워진 배열의 템플릿

14장(디스플레이14.10 및 14.11)에서는

백업이 포함된 double형의 부분적으로 채워진 배열에 대한 클래스 PFArrayDBak을 정의했다.

PFArrayD의 파생 클래스로 정의했다(디스플레이 14.8 및 14.9).

클래스 PFArrayD는 부분적으로 채워진 배열에 대한 클래스였지만 기본 형식인 double에만 작동했다.

디스플레이 16.5 및 16.6에서는

클래스 PFArrayD를 템플릿 클래스 PFArray로 변환하여

배열 기본 형식으로 어떤 형식에서도 작동하도록 한다.

이 프로그램에서는

모든 형식에서 작동하는 백업이 포함된 부분적으로 채워진 배열에 대한

템플릿 클래스 PFArrayBak을 배열 기본 형식으로 정의한다.

템플릿 클래스 PFArrayBak을 템플릿 PFArray의 파생 클래스로 정의한다.

일반적으로 파생 클래스 PFArryDBak으로 시작하여

배열 기본 형식인 double의 모든 발생을 형식 매개 변수 T로 교체하고

클래스 PFArrayD를 템플릿 클래스 PFArray로 교체하고

구문을 완전히 템플릿 구문과 일치하도록 정리하면

거의 자동으로 수행할 수 있다.


템플릿 클래스 PFArrayBak에 대한 인터페이스는 디스플레이 16.8에 나와 있다.

기본 클래스는 단순한 PFArray가 아니라 배열 매개변수를 가진 PFArray<T>이다.

생각해 보면 <T>가 필요하다는 것을 알 수 있을 것이다.

백업이 있는 부분적으로 채워진 T의 배열은 부분적으로 채워진 T의 배열에서 파생된 클래스이다.

T가 어떻게 사용되는지가 중요하다.
템플릿 클래스 PFArrayBak에 대한 구현은 디스플레이 16.9에 나와 있다.
다음은 구현에서 첫 번째 생성자 정의를 재현하는 방법이다:

template<class T>
PFArrayBak<T>::PFArrayBak( ) : PFArray<T>( ), usedB(0)
{
    b = new T[getCapacity( )];
}

 

템플릿 클래스 함수의 정의와 마찬가지로 template<class T>로 시작한다
또한 배열의 기본 형식(새 형식 뒤에 제공됨)은 형식 매개변수 T이다.
다른 세부 사항은 분명하지 않을 수 있지만 이해가 된다.
다음은 다음 행을 고려한다:
PFArrayBak<T>::PFArrayBak( ) : PFArray<T>( ), usedB(0)
템플릿 클래스 함수의 정의와 마찬가지로

정의에는 범위 지정 연산자 앞에 형식 매개 변수가 있는 PFArray<T>가 있지만

생성자 이름은 형식 매개 변수가 없는 단순하고 오래된 PFArrayBak이다.

또한 기본 클래스 생성자는 초기화 PFArray<T>( )에 형식 매개 변수 T를 포함한다.

이는 생성자가 인터페이스의 다음 행과 같이 기본 형식 PFArray<T>와 일치하도록 하기 위한 것이다:
class PFArrayBak : public PFArray<T>
템플릿 클래스 PFArrayBak을 사용한 샘플 프로그램이 디스플레이 16.10에 제공된다.

 

디스플레이 16.8 템플릿 클래스 PFArrayBak 인터페이스

//This is the header file pfarraybak.h. This is the interface for the
//template class PFArrayBak. Objects of this type are partially filled
//arrays of any type T. This version allows the programmer to make a
//backup copy and restore to the last saved copy of the partially filled
//array.
#ifndef PFARRAYBAK_H
#define PFARRAYBAK_H
#include "pfarray.h"

namespace PFArraySavitch
{
	template<class T>
	class PFArrayBak : public PFArray<T>
	{
	public:
		PFArrayBak( );
		//Initializes with a capacity of 50.
        
		PFArrayBak( int capacityValue);
        
		PFArrayBak( const PFArrayBak<T>& Object);
        
		void backup( );
		//Makes a backup copy of the partially filled array.
        
		void restore( );
		//Restores the partially filled array to the last saved version.
		//If backup has never been invoked, this empties the partially
		//filled array.
        
		PFArrayBak<T>& operator =( const PFArrayBak<T>& rightSide);        
		virtual ~PFArrayBak( );
	private:
		T *b; //for a backup of main array.
		int usedB; //backup for inherited member variable used.
	};
    
} // PFArraySavitch
#endif //PFARRAY_H

 

디스플레이 16.9 템플릿 클래스 PFArrayBak 구현

//This is the file pfarraybak.cpp.
//This is the implementation for the template class PFArrayBak. The
//interface for the template class PFArrayBak is in the file
//pfarraybak.h.
#include "pfarraybak.h"
#include <iostream>
using std::cout;

namespace PFArraySavitch
{

	template<class T>
	PFArrayBak<T>::PFArrayBak( ) : PFArray<T>( ),usedB(0)
	{
		b = new T[getCapacity( )];
	}

	template<class T>
	PFArrayBak<T>::PFArrayBak( int capacityValue)
	               : PFArray<T>(capacityValue),usedB(0)
	{
		b = new T[getCapacity( )];
	}
    
	template<class T>
	PFArrayBak<T>::PFArrayBak( const PFArrayBak<T>& oldObject)
	               : PFArray<T>(oldObject),usedB(0)
	{
		b = new T[getCapacity( )];
		usedB = oldObject.getNumberUsed();
		for ( int i = 0; i < usedB; i++)
			b[i] = oldObject.b[i];
	}
    
	template<class T>
	void PFArrayBak<T>::backup( )
	{
		usedB = getNumberUsed( );
		for ( int i = 0; i < usedB; i++)
			b[i] = operator[](i);
	}
    
	template<class T>
	void PFArrayBak<T>::restore( )
	{
		emptyArray( );
		for ( int i = 0; i < usedB; i++)
			addElement(b[i]);
	}
    
	template<class T>
	PFArrayBak<T>& PFArrayBak<T>:: operator =( const PFArrayBak<T>& rightSide)
	{
		PFArray<T>:: operator =(rightSide);
        
		if (getCapacity( ) != rightSide.getCapacity( ))
		{
			delete [] b;
			b = new T[rightSide.getCapacity( )];
		}        
		usedB = rightSide.usedB;
		for ( int i = 0; i < usedB; i++)
			b[i] = rightSide.b[i];
            
		return * this;
	}
    
	template<class T>
	PFArrayBak<T>::~PFArrayBak( )
	{
		delete [] b;
	}
} // PFArraySavitch

 

디스플레이 16.10 템플릿 클래스 PFArrayBak 시연 프로그램

//Program to demonstrate the template class PFArrayBak.
#include <iostream>
#include <string>
using std::cin;
using std::cout;
using std::endl;
using std::string;

#include "pfarraybak.h"
#include "pfarray.cpp"
#include "pfarraybak.cpp"
using PFArraySavitch::PFArrayBak;

int main( )
{
	int cap;
	cout << "Enter capacity of this super array: ";
	cin >> cap;    
	PFArrayBak<string> a(cap);
    
	cout << "Enter " << cap << " strings\n";
	cout << "separated by blanks.\n";
    
	string next;
	for ( int i = 0; i < cap; i++)
	{
		cin >> next;
		a.addElement(next);
	}
	int count = a.getNumberUsed( );
	cout << "The following " << count
	     << " strings read and stored:\n";
	int index;
	for (index = 0; index < count; index++)
		cout << a[index] << " ";
	cout << endl;
    
	cout << "Backing up array.\n";
	a.backup( );
	cout << "Emptying array.\n";
	a.emptyArray( );
	cout << a.getNumberUsed( )
	     << " strings are now stored in the array.\n";
	cout << "Restoring array.\n";
	a.restore( );
	count = a.getNumberUsed( );
	cout << "The following " << count
	     << " strings are now stored:\n";
	for (index = 0; index < count; index++)
		cout << a[index] << " ";
	cout << endl;
    
	cout << "End of demonstration.\n";
	return 0;
}

#include "pfarray.cpp"

기본 클래스 템플릿의 구현을 포함하는 것을 잊지 말기!

 

샘플 대화 상자

Enter capacity of this super array: 3
Enter 3 strings separated by blanks.
I love you
The following 3 strings read and stored:
I love you
Backing up array.
Emptying array.
0 strings are now stored in the array.
Restoring array.
The following 3 strings are now stored:
I love you
End of demonstration.

 

'프로그래밍 공부 > OOP' 카테고리의 다른 글

17장 연계된 자료 구조(2)  (2) 2023.12.01
17 연계된 자료 구조(1)  (1) 2023.12.01
15장 다형성과 가상함수  (1) 2023.11.30
14장 상속  (0) 2023.11.29
13장 재귀  (1) 2023.11.28