C/C++ Template


서론

이 장은 C++에서 사용되는 Template개념에 대해서 설명한다.

목차

  1. Template 기본개념
  2. Class Template
  3. Template Specialization(템플릿 특수화)
  4. 함수의 템플릿(Function Template)
  5. Function Object(Functor)
  6. Meta Programming

Template 기본개념

함수나 클래스를 개별적으로 다시 작성하지 않아도 여러 자료 형으로 사용할 수 있도록 하게 만들어 놓은 틀

함수 템플릿(Function Template)과 클래스 템플릿(Class Template)으로 나눠진다.

Class Template

전체 코드

// 템플릿 첫 활용
#include <iostream>
#include <string>

template <typename T>
class Vector {
  T* data;
  int capacity;
  int length;

 public:
  // 생성자
  Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}

  // 맨 뒤에 새로운 원소를 추가한다.
  void push_back(T s) {
    if (capacity <= length) {
      T* temp = new T[capacity * 2];
      for (int i = 0; i < length; i++) {
        temp[i] = data[i];
      }
      delete[] data;
      data = temp;
      capacity *= 2;
    }

    data[length] = s;
    length++;
  }

  // 임의의 위치의 원소에 접근한다.
  T operator[](int i) { return data[i]; }

  // x 번째 위치한 원소를 제거한다.
  void remove(int x) {
    for (int i = x + 1; i < length; i++) {
      data[i - 1] = data[i];
    }
    length--;
  }

  // 현재 벡터의 크기를 구한다.
  int size() { return length; }

  ~Vector() {
    if (data) {
      delete[] data;
    }
  }
};

int main() {
  // int 를 보관하는 벡터를 만든다.
  Vector<int> int_vec;
  int_vec.push_back(3);
  int_vec.push_back(2);

  std::cout << "-------- int vector ----------" << std::endl;
  std::cout << "첫번째 원소 : " << int_vec[0] << std::endl;
  std::cout << "두번째 원소 : " << int_vec[1] << std::endl;

  Vector<std::string> str_vec;
  str_vec.push_back("hello");
  str_vec.push_back("world");
  std::cout << "-------- std::string vector -------" << std::endl;
  std::cout << "첫번째 원소 : " << str_vec[0] << std::endl;
  std::cout << "두번째 원소 : " << str_vec[1] << std::endl;
}

template <class T>와 template <typename T>는 서로 동일한 의미를 갖는다.

typename 키워드를 사용하기를 권장한다.

template <typename T>
class Vector {
  T* data;
  int capacity;
  int length;
}

Template의 인자에 값을 전달하기 위해서는 < > 키워드를 사용한다.

아래 코드는 Tint라는 type이 전달된다.

Vector<int> int_vec;

따라서 아래 코드처럼 T가 int로 치환된다고 봐도 무방하다. 만약 Vector<std::string>의 경우 T→string으로 치환된다.

class template instantiation : 클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는 것

Template Specialization(템플릿 특수화)

일부 타입(경우)에 대해서 따로 처리하는 것

template <typename A, typename B, typename C>
class test {};

AintCdouble 인 경우에 대해 따로 처리

template <typename B>
template <int, B, double> {};

함수의 템플릿(Function Template)

함수의 경우 클래스에서 사용된 < > 와 달리 변수의 타입을 보고 자동으로 인스턴스화(max<int> (a, b) 해준다.

#include <iostream>
#include <string>

template <typename T>
T max<T& a, T& b) {
	return a > b ? a : b;
}

int main() {
  int a = 1, b = 2;
  std::cout << "Max (" << a << "," << b << ") ? : " << max(a, b) << std::endl;

  std::string s = "hello", t = "world";
  std::cout << "Max (" << s << "," << t << ") ? : " << max(s, t) << std::endl;
}

Function Object(Functor)

아래는 전체 코드다.

#include <iostream>

template <typename T>

class Vector {
    T* data;
    int capacity;
    int length;


public :
    typedef T value_type;

    Vector(int n=1) : data(new T[n]), capacity(n), length(0) {}

    void push_back(int s) {
        if(capacity <= length) {
            int* temp = new T[capacity*2];
            for(int i=0; i< length; i++){
                temp[i] = data[i];
            }
            delete[] data;
            data = temp;
            capacity *= 2;
        }

        data[length] = s;
        length++;
    }

    T operator[](int i){ return data[i];}

    void remove(int x){
        for(int i=x+1; i< length; i++) {
            data[i-1] = data[i];
        }
        length--;
    }

    int size() { return length; }

    void swap(int i, int j) {
        T temp = data[i];
        data[i] = data[j];
        data[j] = temp;
    }

    ~Vector(){
        if(data){
            delete[] data;
        }
    }
};

template <typename Cont>
void bubble_sort(Cont& cont){
    for(int i=0; i < cont.size(); i++){
        for(int j=i+1; j<cont.size(); j++){
            if (cont[i] > cont[j]){
                cont.swap(i, j);
            }
        }
    }
}

template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp){
    for (int i=0; i < cont.size(); i++){
        for (int j=i+1; j<cont.size(); j++){
            if(!comp(cont[i], cont[j])){
                cont.swap(i, j);
            }
        }
    }
}

struct Comp1 {
    bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
    bool operator()(int a, int b) { return a < b;}
};


int main() {
    Vector<int> int_vec;
    int_vec.push_back(3);
    int_vec.push_back(1);
    int_vec.push_back(2);
    int_vec.push_back(8);
    int_vec.push_back(5);
    int_vec.push_back(3);

    std::cout << "정렬 이전 ---- " << std::endl;

    for (int i = 0; i < int_vec.size(); i++) {
        std::cout << int_vec[i] << " ";
    }

    Comp1 comp1;
    bubble_sort(int_vec, comp1);

    std::cout << std::endl << std::endl << "내림차순 정렬 이후 ---- " << std::endl;

    for(int i=0; i < int_vec.size(); i++){
        std::cout << int_vec[i] << " ";
    }
    std::cout << std::endl;

    Comp2 comp2;
    bubble_sort(int_vec, comp2);

    std::cout << std::endl << "오름차순 정렬 이후 ----- " << std::endl;

    for (int i =0; i< int_vec.size(); i++){
        std::cout << int_vec[i] << " ";
    }
    std::cout << std::endl;


}

위 코드 중에서 아래부분을 보면 다음과 같은 부분이 있는데 이는 operator() 만 정의하고 있다.

이 처럼 함수는 아니지만 함수 인 척하는 객체를 함수 객체(Function Object) → Functor 이라고 한다.

template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp) {
  for (int i = 0; i < cont.size(); i++) {
    for (int j = i + 1; j < cont.size(); j++) {
      if (!comp(cont[i], cont[j])) { //이 부분!!
        cont.swap(i, j);
      }
    }
  }
}

struct Comp1 {
    bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
    bool operator()(int a, int b) { return a < b;}
};

Meta Programming

타입은 컴파일 타임에 정해져야 함으로 컴파일 타임에 연산이 완료된다. 이렇게 타입을 가지고 컴파일 타임에 생성되는 코드로 프로그래밍

C++의 경우 템플릿을 가지고 이러한 작업을 하기 때문에 Template Meta Programming(템플릿 메타프로그래밍) 이라고 한다.