서론
함수 템플릿과 클래스 템플릿을 구현해보는 과제이다.
템플릿에 대해 이해하기만 하면 구현은 별로 어려울 것이 없는 과제이다.
https://www.hanbit.co.kr/store/books/look.php?p_code=E6410226806
ex00
다음의 함수 템플릿을 만들어라
- swap : 2개의 arg를 받아서 서로의 값을 바꾼다. 아무것도 리턴하지 않음
- min : 두 개의 arg를 받아서 둘 중 작은 것을 리턴한다. 같으면 두 번째 걸 리턴
- max : 두 개의 arg를 받아서 둘 중 큰 것을 리턴한다. 같으면 두 번째 걸 리턴
세 개의 템플릿은 어떤 타입이 와도 동작해야지만 두 인자의 유형은 동일하며 비교 연산자를 지원한다.
템플릿은 헤더 파일에 정의되어있어야 한다.
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)
{
if (a < b)
return (a);
else
return (b);
}
template<typename T>
T max(T &a, T &b)
{
if (a > b)
return (a);
else
return (b);
}
‘함수 템플릿’이란 template. 즉, 특정한 함수를 찍어내는 견본을 뜻한다. 이 기능이 없다면 swap이라는 함수를 모든 종류의 인자를 받을 수 있게 각각 수십 개를 만들어야겠지만, 이 기능 덕분에 한 번의 구현만으로 수십 개의 함수를 만든 것처럼 되었다. 이렇게 만들어진 함수를 ‘템플릿 함수’라고 한다.
이 기능의 다른 장점은 인자에 자료형 뿐만이 아니라 어떤 클래스가 오더라도 안에서 사용하는 연산을 지원하기만 하면(과제에서는 = > < ) 동일하게 사용할 수 있다는 점이다. 이미 사용해본 cast 함수들이 어떤 자료형이나 클래스가 오더라도 동작하는 걸 보면 이해할 수 있을 것이다.
ex01
세 개의 인자를 가지며 아무것도 반환하지 않는 함수 템플릿을 만든다
- 첫 번째 매개변수는 배열의 주소
- 두 번째 매개변수는 배열의 길이
- 세 번째 매개변수는 배열의 모든 요소에 호출되는 함수
세 번째 함수는 당연히 템플릿 함수일 수도 있다.
template<typename T>
void iter (T *array, size_t length, void (*fn)(T&))
{
for (size_t i = 0; i < length; i++)
{
fn(array[i]);
}
}
template<typename T>
void print(T &str)
{
std::cout << str << " ";
}
template<typename T>
void sqrt(T &num)
{
num = num * num;
}
00과 동일하게 함수 템플릿을 만들되, 함수 포인터를 인자로 받도록 만들면 된다.
조건이 많고 뭔가 어려운 것 같지만 템플릿을 이해했다면 어려울 건 없다. 처음에 인자로 들어오는 함수 포인터를 템플릿 함수로 받을 수 있어야 한다는 부분에서 조금 막막했는데, 그냥 동일하게 만들어도 동작하는 걸 보고 바로 구현하였다.
ex02
서브젝트에서 주어진 main.cpp에 자신의 테스트를 추가하여 테스트를 진행해야 한다.
해당 main 에는 T유형의 인자를 가진 클래스 템플릿 Array을 이용한 여러 가지 테스트가 담겨있는데 해당 클래스에서 구현해야 하는 항목은 다음과 같다.
- 인자 없는 생성자 : 빈 배열을 만듦
- unsigned int n을 인자로 받은 생성자 : n크기의 배열을 만들고 초기화(팁 : int * a = new int(); 를 한 다음 *a를 출력해볼 것
- 복사 연산자와 대입 연산자. 당연히 둘 다 복사 이후에 다른 array에 문제가 생기면 안 됨
- 할당을 할 때 new []를 반드시 사용할 것. 메모리 사전 할당은 금지되며, 당연히 할당되지 않는 메모리에 액세스 해서는 안됨
- [] 연산자를 통해 요소에 접근할 수 있으며 범위를 벗어날 경우 std::exception을 throw 해야 함
- 멤버 함수 size()는 배열의 요소 수를 반환함. 인자를 받지 않으며 인스턴스를 수정해서는 안됨
#ifndef ARRAY_HPP
# define ARRAY_HPP
# include <iostream>
# include <stdexcept>
template <typename T>
class Array {
private:
std::size_t len;
T *array;
public:
Array(void);
Array(std::size_t n);
Array(const Array& obj);
Array& operator=(const Array& obj);
~Array(void);
std::size_t size(void) const;
T& operator[] (std::size_t i);
const T& operator[] (std::size_t i) const;
};
# include "Array.tpp"
#endif
번역을 하면서 무슨 소리인지 이해 안 되는 부분을 먼저 봤는데 int * a = new int(); 를 하면 0을 반환한다. 아마 0으로 초기화가 저절로 된다는 의미인 것 같다.
복사 연산자와 대입 연산자는 깊은 복사를 사용하라는 의미인 것으로 보이고, size 함수는 size_t size(void); 로 선언해두면 될 것 같다.
제출 파일에 Array.tpp를 넣어도 되고 안 넣어도 된다고 되어있는데, 이 파일이 있어도 되고 없어도 되는 이유는 템플릿 파일의 정의와 선언은 헤더에서 동시에 일어나야 하기 때문이다.
일반적으로 클래스는 헤더에는 정의만 들어있고 선언과 구현은 cpp 파일에서 하게 되지만 템플릿 클래스는 클래스를 찍어내기 위한 견본이지 클래스가 아니기 때문에 해당 정보만 가지고 작업을 할 수가 없는 링크 에러가 발생하게 된다.
이를 해결하기 위해서는 헤더 파일 안에서 클래스의 기능을 구현하는 방법이 있다. 이렇게밖에 할 수 없기 때문에 제너럴 룰에서도 Any function implementation put in a header file (except for function templates) means 0 to the exercise.라고 템플릿에서는 헤더에 선언할 수 있도록 규정하고 있다.
구현과 선언이 같은 곳에 있는 게 불편한 사람이 사용하는 것이 tpp 파일이다. 선언부를 만든 tpp에 구현하고 헤더 파일 마지막에 #include “Array.tpp”라고 적으면 된다. 사실 이것도 헤더에 구현하는 것과 동일한 기능을 하지만, 따로 있어야 된다고 생각한다면 이렇게 구현해야 한다.
기존에 구현하던 것과 크게 다르지는 않다. 다만 헤더에 구현하는 것보다 따로 구현하는 게 좋을 것 같다고 생각해서 그렇게 해봤는데, tpp 파일에서 ide의 자동완성이나 오류 체크가 제대로 동작하지 않아서 코딩하는데 조금 애먹었다.
그걸 제외하면 기존에 클래스를 만드는 것과 크게 다를 건 없다고 생각한다. 단, [] 연산자를 구현할 때 const를 같이 구현해줘야 한다. 그렇지 않으면 기본 연산자가 불러와져서 에러를 throw 할 수 없는 일이 생길 수도 있다.
https://cplusplus.com/reference/array/array/operator[]/
https://www.tuwlab.com/ece/22227
'프로그래밍' 카테고리의 다른 글
[42서울] ft_containers[0] - 과제 정리 (1) | 2022.09.24 |
---|---|
[42서울] CPP Module 08 - STL (1) | 2022.09.11 |
[42서울] CPP Module 06 - 형변환 (1) | 2022.09.11 |
[42서울] CPP Module 05 - 재사용성과 예외처리 (3) | 2022.09.10 |
[42서울] CPP Module 04 - 다형성과 추상클래스 (0) | 2022.09.10 |