[Modern C++] 9.1. 템플릿(Template)

윤정민·2023년 7월 7일
0

C++

목록 보기
22/46

1. 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;
}
  • 위 코드의 클래스 부분을 보게되면 클래스에 대해 템플릿을 정의한 것을 볼 수 있음

    • T: 템플릿 인자(어떠한 타입의 이름임)
    template <typename T>
    • 아래에 Class Vector가 오니 Vector클래스에 대한 템플릿을 명시
    class Vector {
    T* data;
    int capacity;
    // ...
  • 템플릿으로 정의된 클래스를 사용

    • <>안에 전달하려는 타입을 적으면 됨
    Vector<int> int_vec;
    • 컴파일러는 전달된 타입을 보고 실제 코드를 생성
    class Vector {
    int* data;
    int capacity;
    int length;
    
     public:
      // 어떤 타입을 보관하는지
      typedef T value_type;
    
      // 생성자
      Vector(int n = 1) : data(new int[n]), capacity(n), length(0) {}
    
      // 맨 뒤에 새로운 원소를 추가한다.
      void push_back(int s) {
        if (capacity <= length) {
          int* temp = new int[capacity * 2];
          for (int i = 0; i < length; i++) {
            temp[i] = data[i];
          }
          delete[] data;
          data = temp;
          capacity *= 2;
        }
    
        data[length] = s;
        length++;
      }
    
      // 임의의 위치의 원소에 접근한다.
      int 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;
        }
      }
    };

    이렇게 클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는 것을 클래스 템플릿 인스턴스화(class template instantiation)라고 함


2. 템플릿 특수화

  • bool을 템플릿의 인자로 사용할 경우 1bit로 처리 가능한 bool이 8bit(c++의 기본처리 단위는 1byte)로 처리되니 엄청난 메모리 낭비가 발생함
    • bool의 경우 따로 처리를 해줘야 함

이와 같이 일부 경우에 대해서 따로 처리하는 것을 템플릿 특수화 라고 한다.

2.1. 사용 방법

  • 다음과 같은 코드를 보자
template <typename A, typename B, typename C>
class test{};
  • A가 int고 C가 double일 때 따로 처리하는 방법

    • 특수화 하고 싶은 부분에 원하는 타입을 전달하고 위에는 일반적인 템플릿을 쓰면 됨
    template <typename B>
    class test<int, B, double>{};

    2.2. bool 해결법

  • 다음과 같이 특수화 해주면 됨

    template <>
    class Vector<bool> {
     ...  // 원하는 코드
    }

Vector을 구현하기 위해 int 배열을 이용한다. 1개의 int는 4byte이므로, 32개의 bool 데이터들을 한데 묶어서 저장 가능하다. 이를 통해 원래 방식대로라면 bool이 1바이트로 저장되지만, 이렇게 하면 bool을 1비트로 정확히 표현 가능해진다.

  • 문제점
    위와 같은 방식으로 메모리 관련 측면에서 효율이 매우 높아지지만, 이를 구현하는 방법은 조금 더 복잡해진다. 왜냐면 int 데이터에서 정확히 한 비트의 정보만 뽑아서 보여주어야 하기 때문이다. 예를 들어 N번째 bool 데이터는 N/32번째 int에 들어가있고, 그 안에서 정확히 N%32번째 비트가 되기 때문이다. 간단하게 생성자만 보자.
  Vector(int n = 1)
      : data(new unsigned int[n / 32 + 1]), capacity(n / 32 + 1), length(0) {
    for (int i = 0; i < capacity; i++) {
      data[i] = 0;
    }
  }

2. 함수 템플릿

  • 똑같이 써주면 됨
    • 인스턴스화 되는 시점은 코드에서 max(a, b)가 호출될 때
template <typename T>
T max(T& a, T& b) {
  return a > b ? a : b;
}

3. 함수 객체(Funtion Object - Functor)의 도입

  • Cont: 클래스를 템플릿 인자로 받음
  • Comp: 함수또한 객체를 받음
  • comp라는 객체가 cont[i]cont[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);
      }
    }
  }
}

3. 템플릿 인자

  • 템플릿을 통해 인자 또한 넘겨줄 수 있음
#include <iostream>

template <typename T, int num>
T add_num(T t) {
  return t + num;
}

int main() {
  int x = 3;
  std::cout << "x : " << add_num<int, 5>(x) << std::endl;
}
                             
profile
그냥 하자

0개의 댓글