상속이나 컴포지트 패턴이 코드를 재활용하는데에 항상 최선의 선택은 아니다. 컴파일 단계에서 정의된 데이터형를 대상으로만 재활용할 수 있다는 점에서 상속과 컴포지트도 한계가 분명히 존재한다. 하지만 클래스 템플릿(Class Template)은 기본 자료형 뿐만이 아닌, 자료형 자체 까지도 재활용 할 수 있게 해준다. 사실 원래 템플릿의 도입 목적은 컨테이너 클래스들을 위해 재활용할 수 있는 코드를 제공하자는 것이었다. 그만큼 템플릿은 다양한 데이터형에 공통으로 적용할 수 있는 프로젝트와 잘 맞는다. 클래스 템플릿은 일반화 프로그래밍으로 확장하기 위한 초석이나 다름없는 문법이므로 이번 포스팅에서는 보다 자세하게 클래스 템플릿의 특성에 대해 정리하고자 한다. 

 

     


    목차

    • 클래스 템플릿의 정의
    • 데이터형 매개변수와 기본 자료형의 혼합 사용
    • 클래스 템플릿의 활용
    • 하나 이상의 데이터형 매개변수 사용하기
    • 데이터형 매개변수에 디폴트값 설정하기
    • 템플릿의 특수화
    • 멤버 템플릿
    • 템플릿 클래스와 프렌드 함수
    • 바운드 템플릿 프렌드 함수 구현
    • 언바운드 템플릿 프렌드 함수 구현

     


     

    클래스 템플릿(Class Template)의 정의

    템플릿은 매개변수로 데이터형을 지정해줄 수 있다. 기본적으로 템플릿을 정의하기 위해서는 <class T> , <typename T> 의 형식으로 많이 사용된다. 여기서 T는 데이터형 매개변수(Type Parameter)이다. 기본적인 코드 형태를 살펴보면 다음과 같다.

     

    // 기본적인 class template 작성 방법
    template <class T>
    class Stack
    {
    private:
    	enum  {MAX = 10};    // 클래스 전용 상수
    	T     m_arr[MAX];    // 객체가 생성될 때 저장할 배열의 자료형 T의 형태가 결정된다.
    	int   top;           // Stack의 마지막 요소 위치를 저장할 변수
    public:
    	Stack();
    	bool isEmpty() const;      // Stack이 비었는지 검사한다.
    	bool isFull() const;       // Stack이 가득찼는지 검사한다.
    	// push()는 Stack이 비어있으면 true, 아니면 false를 리턴한다.
    	bool push(const T& arr_push);  // Stack에 항목을 추가한다.
    	// pop()은 Stack이 비어있으면 false, 아니면 true를 리턴한다.
    	bool pop(T& item);  // item에 Stack 항목을 꺼내서 집어넣는다.
    };
    
    template <typename T>
    Stack<T>::Stack()
    	:top(0)
    {
    	// empty
    }
    
    // 템플릿은 선언이 아니기 때문에 const 함수는 const를 명시해주어야 한다.
    template<class T>  
    bool Stack<T>::isEmpty() const  
    {
    	return top == 0;
    }
    
    template <typename T>
    bool Stack<T>::isFull() const
    {
    	return top == MAX;
    }
    
    template <class T>
    bool Stack<T>::push(const T& item)
    {
    	if (top < MAX)
    	{
    		m_arr[top++] = item;
    		return true;
    	}
    	else
    		return false;
    }
    
    template <typename T>
    bool Stack<T>::pop(T& item)
    {
    	if (top > 0)
    	{
    		item = m_arr[top--];
    		return true;
    	}
    	else
    		return false;
    }

     

    간단한 정적 배열 형식의 Stack을 Template으로 수정한 코드이다. 해당 Stack이 저장하고 관리하는 데이터 타입이 데이터형 매개변수로 작성되어서 자료형에 대해 유동적으로 대응이 가능하다. 유념해야할 것은 템플릿은 선언과 정의가 아니여서 따로 구체화를 하지않으면 컴파일러는 해당 템플릿에 대한 코드를 생성하지 않는다는 점이다. 그리고 이 코드는 int나 double같은 기본적인 데이터형에는 문제없이 코드가 돌아가지만 어떤 특정한 데이터형에 대해서는 코드가 동작하지 않을 수도 있다. 그 부분은 부분적 특수화를 통해 해결이 가능하다. 

     

     


     

    데이터형 매개변수와 기본 자료형의 혼합 사용

    클래스 템플릿데이터형 매개변수(Type Parameter)외에도 기본 자료형을 같이 작성해서 사용할 수 있다. 이처럼 데이터형 매개변수가 아닌 기본적인 자료형을 데이터형이 아닌 매개변수(non-type-argument) or 수식 매개변수(expression argument)라고 한다. 

     

    수식 매개변수는 몇가지 제약이 있는데. 정수형, 열거형, 참조, 포인터가 수식 매개변수가 될 수 있다. 하지만 수식 매개변수의 값을 변경하거나 주소를 얻을 수는 없다. 그래서 n++이나 &n과 같은 표현은 허용되지 않는다. 템플릿은 구체화되기 전까지 실체로 존재하지 않기 때문이다. 그리고 템플릿을 구체화할 때 사용되는 r-value는 const 상수 수식이어야 한다.

     

    만약 생성자를 사용할 때 new와 delete로 이용한 접근 방식을 택한다면 heap영역을 사용하게 되며, 반대로 수식 매개변수를 통한 배열 관리를 채택할 경우 자동 변수들을 관리하는 Stack memory를 사용하게 된다. 보통 크기가 작고 바꿀 일이 없는 배열을 많이 사용할 경우에 수식 매개변수를 통한 Stack memory를 사용한다. 좀 더 빠른 속도를 보장해주기 때문이다.

     

    하지만 수식 매개변수가 가지는 큰 단점이 있다. 바로 크기별로 템플릿을 생성해버린다는 것이다. 이를 테면 두 개의 템플릿을 구체화했다고 상상해보자. 해당 템플릿이 관리하는 배열의 크기를 템플릿의 수식 매개변수로 작업을 한다고 가정하면 해당 배열의 크기마다 템플릿을 개별적으로 전부 생성해버리게 된다. 하지만 해당 템플릿이 new와 delete를 사용하는 동적 배열을 포함할 경우 하나의 템플릿으로 대응이 가능하다. 그리고 다른 크기의 배열로부터 대입을 허용할 수 있게 코드를 디자인할 수 있게 된다.

     

    결론적으로 동적 배열을 사용하는 템플릿은 배열을 사용할 때 크기 조절과 대입이 가능하며, 수식 매개변수를 통한 정적 배열을 사용하는 템플릿은 크기 조절과 대입이 불가능한 대신에 Stack memory를 통한 빠른 실행 속도를 보장받을 수 있다.

     

     


     

    클래스 템플릿의 활용

    클래스 템플릿은 일반적인 클래스처럼 사용할 수 있다. 이를테면 템플릿을 기초클래스로 활용할 수도 있다. 또한 템플릿 클래스 그 자체가 다른 템플릿들의 데이터형 매개변수가 될 수도 있다. 예를 들면 배열 템플릿을 사용하여 스택 템플릿을 구현할 수 있다. 

     

    #include <iostream>
    
    template <typename T> // or <class T>
    class Array
    {
    private:
    	T entry;
    	//...
    };
    
    // 템플릿 부모클래스의 타입을 자식클래스의 타입으로 지정해주며 상속받는다. 
    template <typename Type> // or <class Type>
    class GrowArray : public Array<Type>
    {
    	//...
    };
    
    // 템플릿 Array를 멤버 변수로 사용하는 방법
    template <typename Tp>
    class Stack
    {
    	Array<Tp> ar;   // template Array를 멤버로 사용한다
    };
    
    Array<Stack<int>> asi;  // 스택들의 배열

     

    아래에 Array<Stack<int>> asi 코드처럼 템플릿을 재귀적으로 사용할 수 있다.

     


     

    하나 이상의 데이터형 매개변수 사용하기

    템플릿은 데이터형 매개변수를 하나 이상으로 추가해 사용할 수 있다. 아래는 종류가 전혀 다른 두 값을 저장하기 위해서 사용하는 Pair 템플릿 클래스이다. (표준 템플릿 라이브러리도 이와 비슷한 Pair라는 템플릿을 제공한다.)

     

    // Pair Template - Pair Template을 사용하고 정의한다.
    #include <iostream>
    #include <string>
    
    // 데이터형 매개변수를 2개 가지는 템플릿을 생성한다.
    template <class T1, class T2>
    class Pair
    {
    private:
    	T1 a;
    	T2 b;
    public:
    	T1& first();  // a에 저장된 데이터의 참조형을 리턴 (value 변경에 용이)
    	T2& second(); // b에 저장된 데이터의 참조형을 리턴 (value 변경에 용이)
    	T1 first() const { return a; }  // a의 value를 리턴한다.
    	T2 second() const { return b; } // b의 value를 리턴한다.
    	Pair(const T1& aval, const T2& bval) :a(aval), b(bval) {}
    	Pair() {}
    };
    
    template<class T1, class T2>
    T1& Pair<T1,T2>::first()
    {
    	return a;
    }
    
    template<class T1, class T2>
    T2& Pair<T1, T2>::second()
    {
    	return b;
    }

     

    • T1& first()
      T1 타입 a의 참조형을 리턴해서 내부에 있는 값의 변경이 가능하게 하는 인터페이스다
    • T1 first() const
      내부 value의 값 변경은 허용하지 않으며 단지 출력만을 위한 인터페이스이다.
      const는 출력만 가능하게 하기 위해 값을 a의 value를 변경하지 못하도록 제약을 건 것이다.

     

    이제 이 코드를 사용하는 int main() 함수를 구성해보자.

     

    int main(void)
    {
    	using namespace std;
    	Pair<string, int> univers[4] =
    	{
    		Pair<string, int>("순이", 5),
    		Pair<string, int>("푸바오", 4),
    		Pair<string, int>("행복", 3),
    		Pair<string, int>("천국", 2)
    	};
    
    	// Pair<string, int> 는 임시객체이며 변수명이 없기 때문.
    	int pairSize = sizeof(univers) / sizeof(Pair<string, int>); // 기대값 4
    	cout << "joints : " << pairSize << endl;
    
    	for (int i = 0; i < pairSize; i++)
    	{
    		cout << "univers[i].first(): " << univers[i].first() << "\t";
    		cout << "univers[i].second(): " << univers[i].second() << endl;
    	}
    }

     

    • Pair<string, int> univers[4] { ... }
      univers라는 배열을 생성했다. 해당 배열의 데이터 타입은 Pair 템플릿이며 내부 요소들은 Pair 임시 객체들로 구성되어 있다. 임시객체들을 생성하는 동시에 초기화도 같이 해줄 수 있다.
    • int pairSize = sizeof(univers) / sizeof(Pair<string, int>); 
      univers 배열의 요소 개수를 구하는 코드이다. 한 요소 바이트 크기당 univers에 대한 전체적인 바이트 크기를 구해준다. 단, 배열 내부 요소들은 이름이 없는 임시 객체들이므로 동일한 형태의 새로운 데이터형의 임시객체를 생성해서 각 요소에 대한 바이트 크기를 sizeof에 던져야한다.
    • cout << univers[i].first();
      univers 배열의 각 요소들의 인터페이스에 접근한다. univers[i]는 해당 i 요소의 Pair 참조를 의미하므로 해당 객체에 접근하기 위해 (.)닷 연산자를 통해 접근해준다. #만약 [i]를 써주지 않을 경우 구조체의 주소를 의미하게 되므로 간접 참조 연산자 (->) 를 통해 첫번째 요소의 인터페이스에만 접근할 수 있다. 만약 인덱스 기호 ([]) 를 생략하고 첫번째 요소에 닷 연산자로 접근하고 싶을 경우 (*univers).first(); 로 가능하다. 여기서 참조연산자에 괄호를 붙인 이유는 닷 연산자가 참조연산자보다 우선순위가 높기 때문에 괄호가 사용되었다.

     


     

    데이터형 매개변수에 디폴트값 설정하기

    클래스 템플릿은 데이터형 매개변수에 디폴트 값을 정해줄 수 있다. 만약 사용자가 클래스의 인스턴스를 생성할 때 매개변수중 일부가 비었을 경우 컴파일러는 해당 디폴트형 데이터로 생성하라고 지시하게 된다.

     

    template <class T1, class T2 = int> 
    class Test
    {
         // empty
    };

     

    표준 템플릿 라이브러리는 클래스를 디폴트 데이터형으로 해서 이 기능을 자주 애용하고 있다.

    • 사용시 주의해야할 점
      1. 클래스 템플릿에는 디폴트 설정이 가능하지만 함수 템플릿은 디폴트 설정이 불가하다.
      2. 만약 데이터형이 아닌 기본 자료형일 경우는 클래스, 함수 템플릿 모두 디폴트 설정이 가능하다.

     


     

    템플릿의 특수화

    클래스 템플릿은 암시적 구체화, 명시적 구체화, 명시적 특수화가 있는데 이 3개를 모두 통칭해서 특수화(specialization)이라고 한다. 여기서 특수화는 프로그래머가 템플릿에 자료형을 직접 명시해주어서 실제로 클래스가 코드내에 정의되는 것을 의미한다. 3가지를 살펴보면 아래와 같다.

     

    • 암시적 구체화(implicit instatiation)
      컴파일러가 직접 특수화를 하는 것을 의미한다. 이를테면 프로그래머가 다음과 같이 클래스 템플릿을 선언하게 된다고 가정하자. 
      ArrayTP<int, 100> stuff;   // 암시적 구체화
      컴파일러는 해당 코드를 읽어도 사용자에 의해 객체 생성이 요구되지 않으면 선언과 정의를 하지 않는다. 여기서 중요한 점은 인스턴스를 생성한것도 선언한 것도 아니다. 사용자가 객체를 요구해야 컴파일러가 정의와 동시에 객체를 생성하게된다.

     

    • 명시적 구체화(explicit instantiation)
      암시적 구체화에 키워드 template을 사용해서 클래스를 선언하는 것을 의미한다.특히 이 선언은 템플릿과 동일한 공간에 있어야 유효하니 주의하자.
      template class ArrayTP<string, 100> // 명시적 구체화
      컴파일러는 해당 구문을 보고 객체를 생성하지 않더라도 함수들의 정의를 포함해서 클래스들의 정의를 생성한다.

     

    • 명시적 특수화(explicit specialization)
      명시적 특수화는 해당 템플릿의 설계도에서 프로그래머가 직접적으로 데이터형 매개변수의 형태를 정해주고 또 기능을 재정의할 수 있는 즉 특정 데이터형에 대한 오버라이딩을 의미한다. 
      template <> class Classname<specialized-type-name> { .... };
      template <> class SortedArray<char *> { ... }; 
      앞서 기존에 작성해둔 템플릿 클래스화 특수화중 우선 순위가 높은것은 특수화이다.

     

    • 부분적 특수화(partial specialization)
      부분적 특수화는 템플릿에서 일부 데이터형 매개변수에 대해 직접 데이터형을 지정해주는 것을 의미한다.
      // 기존 템플릿
      template <class T1, class T2> class Pair {...};
      // 부분적 특수화
      template <class T1> class Pair<T1, int> {...};
      template 키워드 앞에있는 <class T1>은 특수화되지 않은 상태의 매개변수를 의미하며, 그 다음 괄호에 있는 <T1, int>에서 int는 직접 데이터형을 정해주는 부분적 특수화를 의미한다. 단, 특수화 되지 않은 상태의 매개변수가 없고 모두 직접 지정을 해주었다면 이것은 template <> 꼴이 되어서 명시적 특수화가 된다. 우선 순위는 가장 특수화가 많이된 순서부터 내림차순이다.

     

    • 포인터를 위한 부분적 특수화
      template <class T> // 기본적인 템플릿 형태

      class Test { ... };
      template <class T*> // 포인터를 위한 특수화 사용
      class Test { ... };
      -> 여기서 포인터가 아닌 데이터형을 사용하면 컴파일러는 기본적인 템플릿 형태를 채택하게 되고 만약 포인터인 데이터형 매개변수일 경우 아래에 특수화된 템플릿을 사용하게 된다. 둘은 명백히 차이가 있으므로 주의하자.

     

    • 템플릿을 재귀적으로 사용하는 특수화
      // 기본 템플릿 

      template <class T1, class T2, class T3> class Trio { ... };
      // T3를 T2로 설정하는 특수화
      template <class T1, class T2> class Trio<T1, T2, T2> { ... };
      // T3와 T2를 T1*로 특수화하기
      template <class T1> class Trio<T1, T1*, T1*> { ... };

     


    멤버 템플릿

    템플릿은 구조체, 클래스, 템플릿 클래스의 멤버가 될 수 있다. 다음 코드를 살펴보자

    // 멤버 템플릿 구현
    #include <iostream>
    using std::cout;
    using std::endl;
    
    template <typename T>
    class beta
    {
    private:
    	template <typename V>  // 템플릿 멤버
    	class hold
    	{
    	private:
    		V val;
    	public:
    		hold(V v = 0) : val(v) { }
    		void show() const { cout << val << endl; }
    		V value() const { return val; }
    	};
    	hold <T> q;    // beta의 데이터형 매개변수 타입의 hold 객체를 생성
    	hold <int> n;  // int형 hold 객체를 생성
    public:
    	beta(T t, int i) : q(t), n(i) { }
    	template <typename U> // 멤버 템플릿 함수
    	U func(U u, T t) { return (n.value() + q.value()) * u / t; }
    	void show() const { q.show(); n.show(); }
    
    };

     

    이 코드처럼 내부에 또 다른 템플릿 멤버 클래스에 대한 설계도를 작성하고 또 그 내부에 템플릿에 대한 객체를 생성한 것을 확인할 수 있다. 다음은 위에 템플릿 클래스를 사용한 int main 함수 코드이다.

     

    int main(void)
    {
    	beta<float> guy(3.5, 3);
    	cout << "T가 float로 설정되었습니다.\n";
    	guy.show();
    	cout << guy.func(10.0, 2.3) << endl;
    	return 0;
    }

     

    beta의 데이터형 매개변수 타입을 float로 설정하고 3.5, 3을 생성자로 각 멤버 템플릿 객체에 초기화하였다.

     


     

    템플릿 클래스와 프렌드 함수

    템플릿 클래스 선언도 프렌드를 가질 수 있다. 특징은 다음 3가지이다.

    • 템플릿이 아닌 프렌드
    • 바운드 템플릿 프렌드
      클래스가 구체화될 때 클래스의 데이터형에 의해 프렌드의 데이터형이 결정되는 형태
    • 언바운드 템플릿 프렌드
      프렌드의 모든 특수화가 그 클래스의 각 특수화에 대해 프렌드일 경우

     

    템플릿이 아닌 프렌드 함수
    template <class T>
    class HasFriend
    {
    	friend void counts(); // 모든 HasFriend 객체(구체화)들에 대한 프렌드 함수 
        ...
    };

     

    이 선언은 해당 템플릿으로 생성된 모든 구체화된 객체들에 대한 프렌드로 만든다. 예를 들어, count() 함수는 HasFriend<int>, HasFriend<string>등 모든 클래스에 대해 프렌드 함수이다. 여기서 요점은 이 프렌드는 템플릿에 종속되어있지 않기 때문에 객체에 의해 호출되지 않으며 객체 매개변수를 가지지 않는다. 이 프렌드는 전역에 있는 객체에 접근할 수 있다. 템플릿은 구체화나 특수화가 되기 이전에는 코드 상에 생성되어 있지 않으므로 다음과 같은 형태는 사용 불가하다.

    ex) friend void report(HasFriend &);  // HasFriend는 정의되어 있지 않으므로 이러한 코드는 사용 불가

     

    만약 이를 사용하기 위해서는 직접 특수화를 진행해주는 방법밖에 없다.

     

    template <typename T>
    class HasFriend
    {
    	friend void report(HasFriend<T> &); // 바운드 템플릿 프렌드
    	...
    };

     

    해당 프렌드 함수처럼 템플릿의 매개변수를 직접 특수화해서 참조형으로 사용하는 형태바운드 템플릿 프렌드 함수라고 한다. HasFriend <int> hf; 처럼 객체를 생성할 경우 report내에 참조형은 int형으로 특수화되며, 같은 논리로 report에 대해 double을 객체할 경우 오버로딩 버전의 report()가 된다. 즉, report() 함수 자체는 템플릿 함수가 아니며, 단지 템플릿 매개변수를 갖는 프렌드 함수일 뿐이다. 즉 사용하려는 프렌드 들에 대해서 명시적 특수화를 정의해주어야 하는 것을 의미한다.

    • void report(HasFriend<short> &) { ... };
    • void report(HasFriend<int> &) { ... };

     

    다음은 바운드 프렌드 함수를 이용한 코드 구현의 예이다.

     

    // 프렌드를 가지는 템플릿 클래스 구현
    #include <iostream>
    using std::cout;
    using std::endl;
    
    template <typename T>
    class HasFriend
    {
    private:
    	T item;
    	static int nCount;
    public:
    	HasFriend(const T& i) : item(i) { nCount++; } // 객체가 생성할 때마다 count+1
    	~HasFriend() { nCount--; }                    // 객체가 소멸할 때마다 count-1
    	friend void counts();                         // 일반적인 프렌드 함수
    	friend void report(HasFriend<T>&);            // 템플릿 매개변수를 가진 프렌드 함수
    };
    
    // 각 특수화는 자신만의 static 멤버 변수를 가진다.
    template <typename T>
    int HasFriend<T>::nCount = 0; // static 정의 방법
    
    // 모든 HasFriend<T> 클래스에 대한 외부 프렌드 함수
    void counts()
    {
    	cout << "int 카운트:\t" << HasFriend<int>::nCount << endl;
    	cout << "double 카운트:\t" << HasFriend<double>::nCount << endl;
    }
    
    // HasFriend<int> 클래스에 대한 템플릿 프렌드 함수
    void report(HasFriend<int>& hf)
    {
    	cout << "HasFriend<int>: " << hf.item << endl;
    }
    
    // HasFriend<double> 클래스에 대한 템플릿 프렌드 함수
    void report(HasFriend<double>& hf)
    {
    	cout << "HasFriend<double>: " << hf.item << endl;
    }
    
    int main(void)
    {
    	// counts 프렌드 함수 실행 테스트
    	cout << "객체 선언 전\ncounts():\n";
    	counts(); 
    	HasFriend<int> hfClass1(10);
    	cout << "\nHasFriend<int> 객체 hfClass1 생성 후\ncounts():\n";
    	counts();
    	HasFriend<int> hfClass2(20);
    	cout << "\nHasFriend<int> 객체 hfClass2 생성 후\ncounts():\n";
    	counts();
    	HasFriend<double> hfClass3(30.3);
    	cout << "\nHasFriend<double> 객체 hfClass3 생성 후\ncounts():\n";
    	counts();
    
    	// report 프렌드 함수 실행 테스트
    	cout << "\n\n#report 함수 실행#" << endl;
    	cout << "counts():\n";
    	counts();
    	cout << "\nreport(hfClass1):\n";
    	report(hfClass1);
    	cout << "\nreport(hfClass2):\n";
    	report(hfClass2);
    	cout << "\nreport(hfClass3):\n";
    	report(hfClass3);
    	cout << "\n## 프로그램 종료 ##" << endl;
    	return 0;
    }

     


     

    바운드 템플릿 프렌드 함수 구현 

    위 코드를 기반으로 프렌드 함수를 템플릿으로 만들 수 있다. 이것을 바운드 템플릿 프렌드라고 한다.  다소 복잡하지만 클래스의 각 특수화는 하나의 프렌드에 일치하는 특수화를 얻게된다. 

     

    1. 함수를 모두 템플릿 함수로 치환하고 함수 원형을 작성할 것
    2. 그 함수 안에서 다시 템플릿들을 프렌드로 선언할 것
    3. 함수들의 정의를 작성할 것

     

    아래는 위에 템플릿이 아닌 프렌드 함수들을 모두 바운드 템플릿 프렌드 함수로 바꾼 코드이다.

    // 프렌드를 가지는 템플릿 클래스 구현
    #include <iostream>
    using std::cout;
    using std::endl;
    
    // 바운드 템플릿 프렌드 함수를 사용하기 위한 함수 원형
    template <typename T> void counts();
    template <typename T> void report(T &);
    
    template <typename TT>
    class HasFriend
    {
    private:
    	TT item;
    	static int nCount;
    public:
    	HasFriend(const TT& i) : item(i) { nCount++; }
    	~HasFriend() { nCount--; }                   
    	friend void counts<TT>();              // int형의 바운드 프렌드 함수 정의
    	friend void report<>(HasFriend<TT>&);            // 템플릿 매개변수를 가진 프렌드 함수
    };
    
    template <typename T>
    int HasFriend<T>::nCount = 0; 
    
    // 템플릿에 종속된 바운드 템플릿 프렌드 함수 counts의 정의
    template <typename T>
    void counts()
    {
    	cout << "카운트:\t" << HasFriend<T>::nCount << endl;
    }
    
    // 템플릿에 종속된 바운드 템플릿 프렌드 함수 report의 정의
    template <typename T>
    void report(HasFriend<T>& hf)
    {
    	cout << "HasFriend<int>: " << hf.item << endl;
    }
    
    int main(void)
    { 
    	HasFriend<int> hfClass1(10);
    	cout << "\nHasFriend<int> 객체 hfClass1 생성 후\ncounts():\n";
    	counts<int>();
    	HasFriend<int> hfClass2(20);
    	cout << "\nHasFriend<int> 객체 hfClass2 생성 후\ncounts():\n";
    	counts<int>();
    	HasFriend<double> hfClass3(30.3);
    	cout << "\nHasFriend<double> 객체 hfClass3 생성 후\ncounts():\n";
    	counts<double>();
    
    	// report 바운드 템플릿 프렌드 함수 실행 테스트
    	cout << "\n\n#report 함수 실행#" << endl;
    	cout << "\nreport(hfClass1):\n";
    	report(hfClass1);
    	cout << "\nreport(hfClass2):\n";
    	report(hfClass2);
    	cout << "\nreport(hfClass3):\n";
    	report(hfClass3);
    	cout << "\n## 프로그램 종료 ##" << endl;
    	return 0;
    }

     

     


    언바운드 템플릿 프렌드 함수 구현 

    앞의 섹션에서 구성한 바운드 템플릿 프렌드 함수들은, 클래스의 외부에서 선언되고 정의된 템플릿의 템플릿 특수화들이다. 예를 들어, int형 클래스의 특수화는 int 함수 특수화를 얻는 방식이었다. 이러한 외부 선언 방식을 클래스 내부에 선언함으로써 언바운드 프렌드 함수를 생성할 수 있다.

     

    #include <iostream>
    using std::cout;
    using std::endl;
    
    template <typename T>
    class ManyFriend
    {
    private:
    	T item;
    public:
    	ManyFriend(const T& i) : item(i) { }
    	template <typename C, typename D>
    	friend void show2(C&, D&);
    };
    
    template <typename C, typename D>
    void show2(C& c, D& d)
    {
    	cout << c.item << ", " << d.item << endl;
    }
    
    int main(void)
    {
    	ManyFriend<int> hfi1(10);
    	ManyFriend<int> hfi2(20);
    	ManyFriend<double> hfdb(10.5);
    	cout << "hfi1, hfi2: ";
    	show2(hfi1, hfi2);
    	cout << "hfdb, hfi2: ";
    	show2(hfdb, hfi2);
    
    	return 0;
    }

     

    각 언바운드된 프렌드 함수들은 각 템플릿 매개변수에 대한 private 접근 권한을 모두 포함하게 되므로 item에 접근이 가능해진다는 것이 특징이다.