CPP Module 07

과제를 통해 배울 수 있는 것

이번 과제에서는 C++의 템플릿에 대해 학습한다. 템플릿을 이용한 여러 함수, 반복자나 배열을 만든다.

template

참고: 템플릿 (C++)
참고: 제네릭 프로그래밍

내가 맨 처음 이 과제를 평가하러 갔을 때 템플릿의 첫인상이 딱 제네릭 타입이었다.

템플릿은 C++에서 제네릭 프로그래밍의 기초를 담당하고 있다. 제네릭 프로그래밍은 데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입을 가질 수 있게 하는 것이다.

즉 템플릿인 T라는 타입이 있다고 하면 이 T에는 int든, char든, double이든 어떠한 자료형이든 들어갈 수 있다.

Exercise 00: Start with a few

functions

이 exercise에서는 swap, min, max 함수를 구현해야 하며, 인자로는 두 가지 변수를 받는다. 이 변수들은 타입이 정해져 있지 않다.

서브젝트를 보면 템플릿은 헤더 파일에 정의되어야 한다고 명시되어 있기 때문에, 해당 함수들을 헤더 파일에 정의해주면 그만이다.

template <typename T>
void swap(T &a, T &b) {
  T tmp = a;
  a = b;
  b = tmp;
}

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

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

template 다음에 꺽쇠 괄호 내부에 템플릿의 별칭을 T라고 지정해주고, 각 함수의 매개 변수들의 자료형으로 지정해주었다. 그러면 abint든 어떠한 자료형으로도 전달될 수 있음을 의미한다.

Exercise 01: Iter

01번 과제는 iter라는 이름의 함수를 구현하는 것이다. 이 함수는 배열의 주소, 배열의 길이, 배열의 각 요소에 적용할 함수를 인자로 받는다.

iter 함수의 어디에 템플릿 타입을 전달받을 수 있을까? 바로 배열의 타입이다. 배열이 int[]일지, char[]일지를 모르니까.

template<typename T>
void iter(T *arr, int len, void (*func)(T const &)) {
  for (int i = 0; i < len; i++) {
    func(arr[i]);
  }
}

Exercise 02: Array

앞의 과제들은 비교적 쉬운 편이었는데, 이번에는 조금 구현할 게 많다.
이번엔 템플릿 클래스 Array를 구현해야 한다. Array에는 T 타입의 요소를 가지고 있으며, 서브젝트에 제시된 부분을 전부 구현해야 한다. 내용들을 살펴보면 대부분 기본적으로 배열의 동작을 구현하라는 것이다.

template <typename T>
class Array {
 public:
  Array();
  Array(unsigned int n);
  Array(const Array<T> &other);
  Array<T> &operator=(const Array<T> &other);
  ~Array();
  T &operator[](unsigned int idx) const;
  unsigned int size() const;

 private:
  T *_data;
  unsigned int _size;

};

template <typename T>
Array<T>::Array() {
  _data = new T[0];
  _size = 0;
}

template <typename T>
Array<T>::Array(unsigned int n) {
  _data = new T[n];
  _size = n;
}

template <typename T>
Array<T>::Array(const Array<T> &other) {
  _data = new T[other._size];
  _size = other._size;
  for (unsigned i = 0; i < _size; i++) {
    _data[i] = other._data[i];
  }
}

template <typename T>
Array<T> &Array<T>::operator=(const Array<T> &other) {
  if (this != &other) {
    delete[] _data;
    _data = new T[other._size];
    _size = other._size;
    for (unsigned int i = 0; i < _size; i++) {
      _data[i] = other._data[i];
    }
  }
  return *this;
}

template <typename T>
Array<T>::~Array() {
  delete[] _data;
}

template <typename T>
T &Array<T>::operator[](unsigned int idx) const {
  if (idx >= _size) {
    throw std::exception();
  }
  return _data[idx];
}

template <typename T>
unsigned int Array<T>::size() const {
  return _size;
}

template <typename T>
std::ostream &operator<<(std::ostream &os, const Array<T> &arr) {
  os << "[";
  for (unsigned int i = 0; i < arr.size(); i++) {
    os << arr[i];
    if (i != arr.size() - 1) {
      os << ", ";
    }
  }
  os << "]";
  return os;
}

번외. 제네릭 VS 템플릿

참고: 제네릭 및 템플릿(C++/CLI)

내가 맨 앞에서 템플릿의 첫인상이 제네릭이라고 했는데, 문득 이 포스트를 작성하다보니 과연 그 둘이 완전히 같을까 하는 생각이 들었다. 그리고 구글링을 조금 해봤는데 역시나 그 둘에 차이점이 있었다.

제네릭 템플릿
런타임 때 자료형이 대체될 때까지 제네릭 컴파일 때 자료형이 특정됨
제네릭 형식이 포함된 어셈블리를 참조할 때 제네릭 형식을 특정 형식으로 대체 가능 컴파일 시, 일반 형식이기 때문에 다른 어셈블리에서 결과 형식을 특수화할 수 없음
동일한 형식 인수를 사용하는 서로 다른 두 어셈블리에서 특수화된 제네릭은 같은 형식 동일한 형식 인수를 사용하는 서로 다른 두 어셈블리에서 특수화된 템플릿은 런타임에 다른 형식
모든 참조형 매개변수에 사용할 수 있는 실행 가능한 코드 조각으로 생성됨 템플릿은 각 자료형에 대해 분리된 런타임 코드를 생성
비형식 템플릿 매개변수(template <int i> C {})를 허용하지 않음 허용
특정 형식에 대한 템플릿의 사용자 지정 구현 허용하지 않음 허용
형식 인수의 하위 집합에 대한 사용자 지정 구현 허용하지 않음 허용
제네릭 형식의 기본 클래스로 형식 매개 변수를 허용하지 않음 허용
지원하지 않음 템플릿-템플릿 매개 변수(template<template<class T> class X> class MyClass) 지원

댓글남기기