출처
아이버 호튼, 『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