표준 템플릿 라이브러리-2

출처

아이버 호튼, 『C++14 STL 철저 입문』, (주) 도서출판 길벗(2017-03-02), 1장

최호성, 『이것이 C++이다』, 한빛미디어(주)(2016-04-20), 385-388p


1.2 템플릿

  • 함수나 클래스를 매개변수로 작성한 명세
  • 컴파일러는 템플릿을 사용해서 필요할 때 함수나 클래스 정의를 생성
  • 정확히는 코드를 생성하는 청사진이나 방법

인스턴스

  • 템플릿으로 생성한 함수나 클래스 정의
  • 템플릿 매개변수 값은 함수나 클래스 정의의 데이터 타입과 같음

템플릿 예제

template <typename T> T & larger(T& a, T& b) { // 책 오타...
    return a > b ? a : b;
}
  • larger 함수 템플릿은 두 인수 중 더 큰 인수를 반환함
  • 컴파일러가 larger()에 사용한 인수에서 타입을 추론

string을 larger()에 집어넣는 예제

std::string first {"To be or not to be"};
std::string second {"This is the question."};
std::cout << larger(first, second) << std::endl;

1-2-1.cpp

// 1-2-1.cpp
#include <iostream>
#include <string>

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

int main(void) {
  std::string first{"To be or not to be"};
  std::string second{"This is the question."};
  std::cout << larger(first, second) << std::endl;
}

1-2-1.cpp 실행결과

./a.out
To be or not to be

클래스 템플릿 예제

template <typename T> class Array {
private:
  T *elements;  // 타입 T의 배열
  size_t count; // 배열 원소의 개수

public:
  explicit Array(size_t arraySize);        // 생성자
  Array(const Array &other);               // 복제 생성자
  Array(Array &&other);                    // 이동 생성자
  virtual ~Array();                        // 소멸자
  T &operator[](size_t index);             // 첨자 연산자
  const T &operator[](size_t index) const; // 첨자 연산자 - 상수 배열
  Array &operator=(const Array &rhs);      // 할당 연산자
  Array &operator=(Array &&rhs);           // 이동 할당 연산자
  size_t size() { return count; }          // count 변수를 위한 접근자
};
  • size_t 타입 별칭은 cstddef헤더에 정의되어 있으며 usigned 정수 타입
  • 타입 T를 원소로 갖는 배열을 위한 템플릿 정의
  • 템플릿의 본문 밖에 멤버 함수를 정의할 때는 반드시 Array라고 써야 함
  • 할당 연산자를 이용하면 Array 객체 하나를 다른 객체에 할당 가능 --> 원래 배열로 할 수 없는 작업
  • operator=()함수를 템플릿 멤버로 선언하지 않으면 할당 연산자가 필요할 때 컴파일러가 기본 할당 연산자를 public으로 함
  • 할당 연산자 사용을 금지하려면 다음과 같이 delete로 지정해야 함

Array &operator = (const Array &rhs) = delete; // 할당 연산자 없음

  • 복제 생성자, 이동 생성자, 복사 할당 연산자, 이동 할당 연산자, 소멸자 중 하나라도 정의해야 한다면
  • 클래스 멤버 다섯 개를 모두 정의해야 하며, 정의하고 싶지 않은 클래스 멤버는 delete로 지정

이동 생성자와 이동 할당 연산자를 구현한 클래스를 이동 시맨틱(move semantic)을 갖고 있다고 얘기함

  • size() 멤버가 클래스 템플릿 안에 구현되어 있으니 기본 동작은 inline이 되며 외부에서 정의할 필요 없음

size_t size() { return count; }

  • 클래스 템플릿의 멤버 함수에 대한 외부 정의는 그 자체가 템플릿이며 헤더 파일에 둠
  • 멤버 함수를 정의하는 탬플릿에서 사용하는 타입 매개 변수는 클래스 템플릿에서 사용한 타입 매개변수와 반드시 일치해야 함

생성자 정의 예제

template <typename T> // 매개변수 T로 정의한 함수 템플릿
Array<T>::Array(size_t arraySize) try : elements {
  new T[arraySize]
}
, count{arraySize} {}
catch (const std::exception &e) {
  std::cerr << "Array 생성자에서 메모리 할당 실패."
            << std::endl; // 책에선 st:cerr라고 되어있음, 또 오타
  rethrow e;
}
  • elements에 메모리 할당하다 예외 발생 경우를 위해 try 블록 함수를 추가
  • 템플릿 타입 매개변수는 생성자 이름을 한정할 때 반드시 쓰임
  • 멤버 이름을 위한 한정자에는 typename 키워드를 사용하면 안 됨

Array 템플릿의 복제 생성자를 inline으로 정의한 예제

template <typename T>
inline Array<T>::Array(const Array &other) try : elements {
  new T[array.count]
}
, count{array.count} {
  for (size_t i{}; i < count; ++i)
    elements[i] = array.elements[i];
}
catch (std::bad_alloc &) {
  std::cerr << "Array 객체 복제를 위한 메모리 할당에 실패했습니다."
            << std::endl;
}
  • 할당 연산자가 타입 T에 대해 동작한다고 가정하고 있는 코드임

class와 typename 키워드는 템플릿 매개변수를 지정할 때 서로 바꿔 쓸 수 있으므로 템플릿을 정의할 때 template나 template 어느 쪽이든 쓸 수 있다. T가 반드시 클래스 타입을 의미하는 것도 아니고 템플릿 타입 인수는 기본 타입과 클래스 타입 둘 다 될 수 있어서 표현력이 더 좋다고 생각하므로 나는 typename을 사용하는 쪽을 좋아한다.

객체를 인스턴스화 하는 코드

Array<int> data{40};

컴파일이 처리하는 세 가지

  • Array 클래스를 위한 정의
  • 생성자 정의 생성
  • 소멸자 생성

  • 클래스 정의는 템플릿 정의와 비슷함
  • 템플릿 정의에서 T를 사용한다면 클래스는 int로 바꿔서 쓴 것

data 객체의 정의에서 본 클래스

class Array<int> {
private:
  int *elements;
  size_t count;

public:
  explicit Array(size_t arraySize);
  virtual ~Array();
}

타입 별칭으로 템플릿 정의하기

template<typename T> using ptr = std::shared_ptr<T>;

  • std::shared_ptr<T>를 별칭으로 ptr<T>`로 한 것

using std::string;

이걸 쓰면 std::shared_ptr<std::string>

ptr<string>으로 쓸 수 있음

1-2-2.cpp

// 1-2-2.cpp
#include <iostream>
#include <cstring>

template <typename T> class Array {
private:
  T *elements;  // 타입 T의 배열
  size_t count; // 배열 원소의 개수

public:
  explicit Array(size_t arraySize); // 생성자

  Array(const Array &other); // 복제 생성자

  Array(Array &&other); // 이동 생성자

  virtual ~Array() { // 소멸자
    delete[] elements;
  }

  T &operator[](size_t index) { // 첨자 연산자
    if (index < 0 || index >= count) {
      std::cout << "경계를 벗어납니다." << std::endl;
      exit(1);
    }
    return elements[index];
  }

  const T &operator[](size_t index) const { // 첨자 연산자 - 상수배열
    return operator[](index);
  }

  Array &operator=(const Array &rhs) { // 할당 연산자
    if (this == &rhs)
      return *this;

    delete elements;
    elements = new T[rhs.count];
    std::memcpy(elements, rhs.elements, sizeof(T) * rhs.count);
    count = rhs.count;

    return *this;
  }

  Array &operator=(Array &&rhs) { // 이동 할당 연산자
    elements = rhs.elements;
    count = rhs.count;
    rhs.elements = nullptr;
    rhs.count = 0;
  }

  size_t size() { return count; } // count 변수를 위한 접근자
};

// 생성자 외부 정의
template <typename T> // 매개변수 T로 정의한 함수 템플릿
Array<T>::Array(size_t arraySize) try : elements {
  new T[arraySize]
}
, count{arraySize} {}
catch (const std::exception &e) {
  std::cerr << "Array 생성자에서 메모리 할당 실패."
            << std::endl; // 책에선 st:cerr라고 되어있음, 또 오타
  throw e;
}

// 복제 생성자 외부 정의
template <typename T>
inline Array<T>::Array(const Array &other) try : elements {
  new T[other.count]
}
, count{other.count} {
  for (size_t i{}; i < count; ++i)
    elements[i] = other.elements[i];
}
catch (std::bad_alloc &) {
  std::cerr << "Array 객체 복제를 위한 메모리 할당에 실패했습니다."
            << std::endl;
}

int main(void) {
  // int 자료형 배열
  Array<int> arr{5};

  arr[0] = 10;
  arr[1] = 20;
  arr[2] = 30;
  arr[3] = 40;
  arr[4] = 50;

  for (int i = 0; i < 5; ++i)
    std::cout << arr[i] << ' ';

  std::cout << std::endl;

  Array<int> arr2{3};
  arr2 = arr;
  for (int i = 0; i < 5; ++i)
    std::cout << arr2[i] << ' ';

  std::cout << std::endl;
}

1-2-2.cpp 실행결과

./a.out
10 20 30 40 50
10 20 30 40 50
  • arr1은 5개 요소, arr2는 3개 요소
  • arr2 = arr1을 하였는데 arr2의 배열 개수가 5개
Array &operator=(const Array &rhs) { // 할당 연산자
    if (this == &rhs)
      return *this;

    delete elements;
    elements = new T[rhs.count];
    std::memcpy(elements, rhs.elements, sizeof(T) * rhs.count);
    count = rhs.count;

    return *this;
  }
  • 이 코드 덕에 가능한 것

Comments