정신과 시간의 방
작성일
2023. 2. 15. 22:58
작성자
risehyun

01. 함수 템플릿

  • 템플릿이란?
    - 함수나 클래스를 타입에 관계없이 일반화하여 정의할 수 있는 기능을 의미합니다.
    - 컴파일 타임에 클라이언트가 여러 타입의 함수나 클래스를 쉽게 생성하도록 해줍니다.
    - 일종의 코드 생성의 설계도로, 다양한 타입에 따라 하나의 코드로 공통적인 동작을 수행하도록 자동으로 맞춤형 코드를 생성하는 틀 역할을 합니다.
    - 템플릿을 활용하면 서버 프로그래머는 좀 더 일반적인 프로그램 코드를 만들고, 클라이언트에서 구체적 코드를 작성하는 제너릭 한 프로그래밍이 가능해집니다.

  • 템플릿의 종류
    - 함수 템플릿과 클래스 템플릿이 있습니다.

    1) 함수 템플릿 (Function Template) : 여러 타입에서 공통적으로 사용할 수 있는 함수 정의
    2) 클래스 템플릿 (Class Template) : 여러 타입에서 공통적으로 사용할 수 있는 클래스 정의


  • 구체적으로 템플릿이 왜 유용한가?
    - 예시로 뭐든지 두개의 자료형을 입력하면 그것을 출력하는 프로그램을 만든다고 가정해 봅시다.
    - Print()라는 함수를 함수 오버로딩을 이용해 각 자료형 별로 만들어서 프로그램을 구현할 수 있습니다.
    - 하지만 이 함수 오버로딩은 클라이언트가 매개변수 타입을 미리 알고 있다는 전제로 만들어집니다.
    - 만일 클라이언트에서 사용자 정의 타입 등 타입을 결정해야 한다면? 어떤 타입으로 함수 코드를 호출할지 알 수 없습니다.
    - 이때 템플릿 함수를 사용하면 컴파일러가 클라이언트의 함수 호출 인자 타입을 보고 알아서 템플릿 함수의 매개변수 타입을 결정하여 실제 함수인 *템플릿 인스턴스 함수를 만들어 낼 수 있으므로 이 문제를 간편하게 해결할 수 있습니다.

    * 템플릿 인스턴스 함수 : 컴파일러가 생성한 함수 정의 코드


  • 함수 템플릿의 사용
    - 함수 템플릿은 함수 앞에 template<T> 키워드를 붙여 사용합니다.
    - 여기서 T는 클라이언트가 매개변수 타입을 결정할 수 있도록 타입을 일반화(T 타입)한 것입니다.
    - 함수 형식은 template<typename T> void Print(T a, T b); 입니다. 
// 함수 템플릿
#include <iostream>
using namespace std;

template<typename T>
void Print(T a, T b)
{
    cout << a << ", " << b << endl;
}

int main()
{
    Print(10, 20);
    Print(0.123, 1.123);
    Print("ABC", "abcde");
    
    return 0;
}

 

    - 함수 템플릿은 명시적 타입 지정 후 호출이 가능합니다.

#include <iostream>
using namespace std;

template<typename T>
void Print(T a, T b)
{
    cout << a << ", " << b << endl;
}

int main()
{
    Print<int>(10, 20);
    Print<double>(0.123, 1.123);
    Print<const char*>("ABC", "abcde");
    
    return 0;
}


    - 템플릿도 함수처럼 매개변수를 여러 개 가질 수 있습니다.

template<typename T1, typename T2>
void Print(T1 a, T2 b)
{
    cout << a << ", " << b << endl;
}


    - 템플릿의 매개변수 타입에는 템플릿 함수 정의의 연산이 가능한 객체, 즉 인터페이스를 지원하는 객체라면 모두 올 수 있습니다.
    - 예를 들어 복사 생성자를 호출하는 문장이 있다면 복사 생성자(인터페이스)를 지원해야 합니다.
    - 이 조건을 만족하는 모든 경우에 대해 매개변수 타입 지정이 가능하므로, 아래와 같이 typename이 아닌 정수도 가능합니다.
    - 단, 이때 클라 코드에서 PrintArray<int, 5>(arr1)처럼 명시적으로 호출해 컴파일러가 템플릿 인스턴스를 생성하게 해야 합니다.
    - 함수 인자가 arr1라는 정보만을 제공하므로 5라는 정수형 매개변수 인자가 뭔지 컴파일러가 자체 추론할 수 없기 때문입니다.

// 배열 출력 함수 템플릿

#include <iostream>
using namespace std;

template<typename T, int size>
void PrintArray(T* arr)
{
    for (int i = 0; i < size; ++i)
    {
        cout << "[" << i << "]: " << arr[i] << endl;
    }
    cout << endl;
}

int main()
{
    int arr1[5] = { 10, 20, 30, 40, 50 };
    PrintArray<int, 5>(arr1); // 명시적 호출
    
    double arr2[3] = {1.1, 2.2, 3.3};
    PrintArray<double, 3>(arr2); // 명시적 호출
    
    return 0;
}

 

  • 함수 템플릿 특수화
    1) 개념
    - 일반적인 템플릿 함수가 있지만 특정 타입에 대해서는 일반적인 구현과 다르게 동작하도록 별도로 구현하는 것을 말합니다.
    - 즉, "모든 타입에 대해 일반적인 로직을 제공하지만, 어떤 특정 타입에서만 특별한 처리를 하고 싶을 때" 사용할 수 있습니다.

    2) 문법
    - 함수 템플릿 특수화에서는 template<>를 붙이고, 함수 이름 옆에 <타입>을 써서 어떤 타입에 특수화하는지 명시합니다.

    특정 타입에 대한 특수화 - 예: char*

    template<>
    void Print<char*>(char* value) 
    {
        std::cout << "char* 특수화: " << value << std::endl;
    }

    3) 구체적으로 함수 템플릿 특수화라는 것이 왜 필요한가?
    - 컴파일러가 함수 템플릿 인스턴스를 만들려면 템플릿의 매개변수 타입 객체가 템플릿 함수 정의의 연산을 지원해야 합니다.
    - 그러나 소스코드로 지원되지 않는 라이브러리 상태 등 사용하고자하는 클래스를 수정하지 못하는 상태일 때가 있습니다.
    - 그때 사용할 수 있는 것이 바로 함수 템플릿 특수화입니다.
    - 그 밖에도 템플릿은 범용적이지만, 다음과 같이 어떤 타입에서는 그 범용 로직이 적절하지 않은 경우들이 있습니다.

     
    1. 포인터 타입에 대한 출력은 일반 타입과 다르게 메모리 주소가 아닌 문자열 내용을 보여줘야 할 수 있습니다.
    2. 부동소수점 비교는 일반적인 == 비교가 적절치 않아서 오차 허용을 두고 비교해야 할 수도 있습니다.
    3. 클래스의 구조가 너무 달라서 일반 템플릿으로는 처리할 수 없는 경우도 있습니다.


    이처럼 특정 타입만 특별하게 다뤄야 할 이유가 명확할 때, 템플릿 특수화로 제어권을 직접 가져오는 것이 유리합니다.
#include <iostream>
using namespace std;

// 일반 템플릿 함수
template<typename T>
void Print(T value) 
{
    cout << "일반 템플릿: " << value << endl;
}

// 특수화된 템플릿 함수 (char*에 대해)
template<>
void Print<char*>(char* value) 
{
    cout << "char* 특수화: " << value << endl;
}

 

 

02. 클래스 템플릿

- 클래스 템플릿은 클래스를 만들어내는 틀(메타 클래스 코드)를 의미합니다.

- 함수 템플릿과 별반 다르지 않고 단지 함수에서 클래스로 바뀐 형태입니다.
- 아래는 클래스 템플릿을 이용해 자료를 저장, 관리하는 Array 클래스 예제입니다.

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

template<typename T> // 클래스 템플릿 Array 정의
class Array
{
  T *buf;
  int size;
  int capacity;
 
public:
    explicit Array(int cap = 100) : buf(0), size(0), capacity(cap)
    {
        buf = new T[capacity];
    }
    
    ~Array() { delete [] buf };
    
    void Add(T data)
    {
        buf[size++] = data;
    }
    
    .
    .
    .
 
}

 

- 이렇게 정의된 클래스 템플릿을 원하는 타입에 따라 사용할 수 있습니다.

- 함수 템플릿과 마찬가지로 클래스 템플릿 역시 템플릿 자체는 클래스의 메타 코드일 뿐이기 때문에 실제로 클래스(클래스 정의 코드)를 생성하는 역할은 컴파일러가 하게 됩니다.

int main()
{
    Array<int> iarr;
    iarr.Add(10);
    
    Array<double> darr;
    darr.add(10.12);
    
    Array<string> sarr;
    sarr.Add("abc");


    return 0;
}

 

- 템플릿의 매개변수도 함수의 매개변수처럼 디폴트 매개변수 값을 지정할 수 있습니다.

template<typename T=int, int capT=100>
class Array
{
    T* buf;
    int size;
    int capacity;
    
    .
    .
    .

}

int main()
{
    Array<> iarr; // 명시된 타입과 값이 없으므로 디폴트 매개변수값 int, 100을 자동 사용하게 됩니다.
 
    Array<double> darr; // 타입은 명시되어 있지만 값이 없으므로 디폴트 매개변수값 100을 사용합니다.
    
    Array<string, 10> sarr; // 타입과 값 모두 명시되어 있어 디폴트 매개변수값을 사용하지 않습니다.
}

 

 

- 또한, 클래스 템플릿 역시 특수화가 가능합니다.

- 클래스 템플릿 특수화는 함수 템플릿 특수화처럼 일반 버전의 템플릿을 사용할 수 없는 경우나 성능 개선이나 특수한 기능 등을 위해 특수화 버전을 별도로 제공하고자 할 때 사용합니다.

// 일반 템플릿 클래스 예시
template<typename T>
class ObjectInfo
{
    T data;
    
public:
    objcetInfo(const T& d) : data(d) { }
    
    .
    .
    .
    
}


// 특수화 템플릿 클래스 예시
template<>
class ObjectInfo(string)
{
	string data;
    
public:
    ObejctInfo(const string& d) : data(d) { }
    
    .
    .
    .
}

 

'C,C++ > [서적] 뇌를 자극하는 C++ STL' 카테고리의 다른 글

Chapter06-01 시퀀스 컨테이너 : vector  (0) 2023.02.20
Chapter05. STL 소개  (0) 2023.02.16
Chapter03. 함수 객체  (0) 2023.02.15
Chapter02. 함수 포인터  (0) 2023.02.14
Chapter01. 연산자 오버로딩  (0) 2023.02.13