Chapter 0
Template
이번 과제의 주제는 template이다. 템플릿은 함수나 클래스를 개별적으로 다시 작성하지 않아도, 여러 자료 형으로 사용할 수 있게 만들어 놓은 "틀"이라고 할 수 있다.
템플릿은 C++이 가지고 있는 프로그래밍 언어의 특징 중 하나인 일반화 프로그래밍의 기능 중 하나이다. 일반화 프로그래밍이란 데이터를 중시하는 객체 지향 프로그래밍과 달리 프로그램의 알고리즘에 중점을 두고 있다.
C++에서의 템플릿은 크게 함수 템플릿과 클래스 템플릿으로 나눌 수 있다. 먼저, 함수 템플렛은 함수의 일반화된 선언을 뜻하며, 서로 다른 타입에서 동작하는 함수를 한 번에 정의할 수 있다. 임의의 타입으로 작성된 함수에 특정 타입을 매개변수로 전달하면, C++ 컴파일러는 해당 타입에 맞는 함수를 생성해주게 된다.
함수 템플릿
함수 템플릿은 다음과 같은 문법으로 정의한다.
template<typename T>
void function(T& a, T& b) {
/* add code */
}
위에서 정의된 타입 이름은 함수의 원형과 본체에서 임의의 타입으로 사용할 수 있다. 이렇게 정의된 함수 템플릿을 호출할 때 매개변수로 int형을 사용한다면, 함수의 원형과 본체에서 정의된 타입의 이름은 모두 int형으로 바뀌게 된다.
- 함수 템플릿의 인스턴스화이렇게 생성된 인스턴스는 해당 타입에 대해 특수화된 템플릿 함수이다. 이 인스턴스는 함수 템플릿에 해당 타입이 사용될 때마다 호출된다.
- 함수 템플릿이 각각의 타입에 대해 처음으로 호출될 때, C++ 컴파일러는 해당 타입의 인스턴스를 생성한다.
- 명시적 특수화컴파일러는 호출된 함수에 정확히 대응하는 특수화된 정의를 발견하면, 더는 템플릿을 찾지 않고 해당 정의를 사용한다.
이런식의 예제가 있다고 하자. int형으로 사용한 Swap함수는 원형 템플릿에 의해 값이 바뀔 것이다. 하지만 double형의 경우, 함수 내부에서 값을 바꾸는 코드를 삭제하여 값이 바뀌지 않을 것이다./* double형을 위한 명시적 특수화 */ template <> void Swap<double>(double&, double&) { /* double 형은 값을 바꾸지 않도록 한다. */ };
/* 함수 템플릿 원형 예시 */ template <typename T> void Swap(T& a, T& b) { T temp; temp = a; a = b; b = temp; }
- 이런식으로 명시적 특수화를 사용한다면, 사용자가 원하는 타입에 대해 특별한 동작을 정의할 수 있는 기능을 제공한다.
- 명시적 특수화의 예시는 다음과 같다.
- C++의 함수 템플릿은 특정 타입에 대한 명시적 특수화를 제공하여 해당 타입에 대해 특별한 동작을 정의할 수 있게 해준다.
클래스 템플릿
클래스 템플릿은 클래스의 일반화된 선언을 뜻한다. 앞서 본 함수 템플릿과 동작은 같지만, 그 대상이 함수가 아닌 클래스라는 점이 다를 뿐이다.
클래스 템플릿을 사용한다면, 타입에 따라 다르게 동작하는 클래스 집합을 만들 수 있게 된다. 즉, 클래스 템플릿에 전달되는 템플릿 인수에 따라 별도의 클래스를 만들 수 있다. 이런 템플릿 인수의 경우, 타입이거나 명시된 타입의 상수값일 수 있다.
클래스 템플릿은 다음과 같은 문접으로 정의한다.
template <typename T>
class Class {
private:
T _data;
public:
Class(T data);
T getData();
};
int main() {
Class <int> intClass = 0;
Class <double> doubleClass = 0.0;
}
클래스 템플릿으로부터 객체를 생성할 때는 템플릿에 전달되는 인수 타입을 명시해주어야 한다.
전달된 매개변수의 타입을 가지고 컴파일러가 해당 타입에 맞는 함수를 생성해주는 함수 템플릿과 달리, 클래스 템플릿은 사용자가 사용하고자 하는 타입을 명시적으로 제공해야 한다.
- 중첩 클래스 템플릿
/* 중첩 클래스 템플릿 */ template <typename T> class X { template <tyepname U> class Y { /* add code */ } /* add code */ }
- 클래스 혹은 클래스 템플릿 내부에 또 다른 템플릿을 중첩하여 정의할 수 있다. 이런 템플릿을 멤버 템플릿이라 한다.
클래스 템플릿의 특징은 다음과 같다.
- 하나 이상의 템플릿 인수를 가지는 클래스 템플릿을 선언할 수 있다.
/* 두 개의 템플릿 인수를 가지는 클래스 템플릿을 선언함 */ template <typename T, int i> class X { ... }
- 클래스 템플릿에 디폴트 템플릿을 명시할 수 있다.
/* */ template <typename T = int, int i> class X { ... }
- 클래스 템플릿을 부모 클래스로 하여 상속할 수 있다.
template <typename T> /* 클래스 템플릿 X를 상속받음 */ class Y : public X <T> { ... }
클래스 템플릿에도 명시적 특수화가 가능하다. 또한, 템플릿 인수가 두 개 이상이고, 그 중 일부에 대해서만 특수화를 해야할 때는 부분 특수화를 사용할 수 있다. 부분 특수화는 명시적 특수화의 미완성 형태라고 생각하면 될 것이다.
Chapter 1
Introduction
이 과제는 내가 걸어가야할 C++이라는 여행길에 시작점에 객체지향프로그래밍이 어떤건지를 설명해주는것이 목적이라고 한다.
객체지향은 많은 언어들이 사용하는데, 왜 C++을 배우게 되느냐.. 하면 나의 오랜 친구(42에서만)인 C에서 파생된 언어이기 때문이라고 한다.
C++은 복잡한 언어이기 때문에, 단순하게 유지하기 위해 내 코드를 C++ 98 standard로 컴파일 할것이라고 한다.
Chapter 2
General Rules
컴파일
- 너의 C++ 코드는 "-Wall -Wextra -Werror" 플래그와 함께 컴파일 한다.
- 너의 코드는 "-std=c++98"를 플래그로 추가하고도 컴파일이 되어야 한다.
포멧과 네임 컨벤션
- 모든 문제의 디렉토리는 다음의 방식을 따른다 : ex00, ex01, ex02 ... exn
- 너의 파일, 클래스, 함수, 멤버함수, 속성들은 가이드라인의 요구에 맞추어야 한다.
- 클래스 이름은 UpperCamelCase 형식을 사용한다. 클래스를 담은 파일의 이름은 클래스 이름을 따라야 한다.
- 얘를 들어, ClassName.hpp/ClassName.h, ClassName.cpp, ClassName.tpp 와 같은 형식이다. 그렇다면 "BrickWall"이라는 클래스를 담고있는 헤더의 이름은 BrickWall.hpp가 되어야 하는 것이다.
- 달리 명시하지 않았을 경우, 모든 출력 메세지는 개행문자로 끝나야 하며 표준 출력으로 보여주어야 한다.
- Goodbye Norminette! C++ 모듈에서는 코딩 스타일을 강요하지 않는다. 너가 좋아하는 것을 따라 할 수 있다. 하지만 동료평가시, 이해할 수 없는 코드를 사용할 경우, 동료들이 점수를 줄 수 없다는 것을 명심해라. 최선을 다해 클린하고 가독성 있는 코드를 작성하길 바란다.
허용되는 사항 / 금지되는 사항
너는 이제부터 C로 코딩하지 않는다. C++의 시간이다 ! 그러므로:
- 너는 표준 라이브러리에서의 거의 모든 것들을 사용할 수 있다. 따라서, 이미 알고 있는 것을 고수하는 대신, 익숙한 C함수의 C++ 버전을 최대한 많이 사용해보는 것이 현명할 것이다.
- 그러나, 너는 다른 외부 라이브러리를 사용할 수 없다. 이 말의 의미는 C++ 11과 Boost 라이브러리가 금지된다는 것이다. 다음 함수도 금지된다. printf(), alloc() and free(). 이걸 쓰면 너의 점수는 0점이 될 것이다.
- 달리 명시하지 않는 이상, namespace 및 friend 키워드의 사용은 금지된다. 사용하면 -42점이므로 주의하자.
- 오직 모듈 08에서만 STL의 사용이 허용된다. 이 뜻은, 컨테이너(vector, list, map and forth)와 알고리즘(<algorithm> 헤더가 포함된 어떠한 것들)이 허용되지 않는다는 것이다. 이 또한 사용하면 -42점이다.
몇 가지 디자인 요구사항
- 메모리 누수가 발생하는것은 C++도 동일하다. 만약 너가 메모리를 할당(new 키워드 사용으로)하면, 메모리 누수를 반드시 피해야한다.
- 모듈 02부터 08까지는 달리 명시된 경우를 제외하고, 클래스를 Orthodox Canonical Form으로 설계해야만 한다.
- 헤더파일에 선언되지 않은 함수들을 사용하는 경우 0점을 받는다.
- 각 헤더를 다른 헤더와 독립적으로 사용할 수 있어야 한다. 따라서 필요한 모든 종속성을 포함해야한다. 그러나 헤더가드를 사용해 이중으로 포함되는 것은 피해야만 한다. 그렇지 않으면 0점을 받을 것이다.
Read me
- 너는 필요(즉, 코드를 분할하기 위해)에 의해 파일을 추가할 수 있다. 이런 할당은 프로그램에서 확인하지 않으므로, 필수 파일을 제출하는 한 자유롭게 추가하면 된다.
- 때때로 과제의 가이드라인이 짧아보이지만, 명시적으로 작성되지 않은 요구사항들을 example에서 보여줄 수 있다.
- 시작 전에 꼭 모듈의 과제를 읽어야만 한다. 정말이다 !
Chapter 3
Ex00
문제
다음 함수들의 템플릿을 구현해라:
- swap: 주어지는 두 개의 인자의 값을 바꾼다. 어떠한 것도 반환하지 않는다.
- min: 인자로 전달된 두 값을 비교한 뒤, 더 작은 값을 반환한다. 만약 두 값이 똑같다면, 두번째로 들어온 것을 반환한다.
- max: 인자로 전달된 두 값을 비교한 뒤, 더 큰 값을 반환한다. 만약 두 값이 똑같다면, 두번째로 들어온 값을 반환한다.
이 함수들은 모든 유형의 인자로 호출할 수 있다. 유일한 요구사항은 두 인자들이 같은 타입이어야 하고 모든 비교 연산자들을 지원해야 한다는 것이다.
템플릿은 헤더파일에 정의해야 한다.
구현
00번 예제는 정말 간단한 함수들을 구현하면 되는 문제였다. 그러므로 생략 ...
주의할 점은 min, max함수들이 같은 값이 들어왔을 경우 두번째 값이 반환되어야 한다는 것이다. 정상적으로 동작하는지 확인하기위해 같은 값들을 넣어주고, 반환되는 값의 주소값들을 비교했다.
Ex01
문제
3개의 인자를 받고 반환값은 없는 반복 함수 템플릿을 구현해라.
- 첫번째 인자는 배열의 주소이다.
- 두번째 인자는 배열의 길이이다.
- 세번째 인자는 배열의 모든 요소에 호출될 함수이다.
너의 테스트를 포함하는 main.cpp파일을 제출해라. 충분한 코드를 제공해라.
너의 iter 함수 템플릿은 배열의 어떤 타입도 동작 가능해야한다. 세번째 인자는 인스턴스화된 함수 템플릿일 수 있다.
구현
이번 문제도 간단하다. 반복문을 수행하는 iter 함수를 만들어준 뒤, 그 함수를 테스트할 수 있는 main.cpp 파일을 작성해주면 된다.
template <typename T>
void iter(const T* array, std::size_t lengthOfArray, void (*f)(const T &)) {
for (std::size_t idx = 0; idx < lengthOfArray; ++idx) {
f(array[idx]);
}
}
과제의 요구에 맞게 인자들을 구성해준 뒤, 각각의 요소들마다 세번째 인자로 들어오는 함수를 적용할 수 있도록 만들어주었다.
Ex02
문제
T 타입의 요소를 포함하는 클래스 템플릿인 Array를 개발하고 다음의 행동과 함수들을 구현해라:
- 인자가 없는 생성자: 빈 배열이 생성됨.
- unsigned int n을 인자로 받는 생성자: 기본값으로 초기화되는 n개 요소의 배열이 생성됨.
- 팁: int * a = new int(); 를 컴파일한 뒤, *a를 표시한다.
- 복사생성자와 할당 연산자 생성자. 두 경우 모두 원본이나 복사 후 사본을 수정해도 다른 배열에 영향을 미치지 않아야 한다.
- new[] 연산자를 사용하여 메모리를 할당해야 한다. 예방적인 할당(미리 메모리를 할당하는 행위)은 금지된다. 프로그램은 할당되지 않은 메모리에 절대 접근하지 않아야 한다.
- 요소들은 연산자 [ ] 를 통해 접근할 수 있다.
- [ ] 연산자로 요소에 접근할 때, 인덱스가 범위를 벗어나면 std::exception이 발생한다.
- 배열의 요소 수를 반환하는 멤버 함수 size().
이 멤버 함수는 매개변수를 사용하지 않으며 현재 인스턴스를 수정하지 않아야 한다. 평소와 같이 모든 것이 예상대로 작동하는 지 확인하고 테스트가 포함된 main.cpp 파일을 제출해라.
구현
마지막 예제는 배열을 구현하는 문제이다. 이 문제를 통해 클래스 템플릿을 배우는 것이 목표이며, 모듈 07 중 가장 할 것이 많은 문제였다.
문제를 보면, Array.hpp를 만들고, 선택사항으로 Array.tpp를 만들어도 된다는 정보가 있다. 이 뜻은, 원착상 템플릿의 경우 헤더 파일에서 정의를 해주어야 하는데, 클래스 템플릿을 헤더 파일에서 정의하면 양이 꽤 많아져서 tpp 파일을 통해 cpp파일에 정의하는것 처럼 분리해도 된다는 뜻인데.. 나는 그냥 hpp파일에 모두 정의해주었다.
먼저, 과제의 요구대로 구현을 시작해주었다.
/*
* A default constructor
TODO: Constuction with no parameter: Creates an empty array.
*/
Array() : _array(NULL), _arraySize(0) {
}
인자가 들어오지 않는 경우, 빈 배열을 만든다라고 하는데, 0의 크기를 가진 빈 배열이라면 결국 NULL 주소를 가리기는 것이 맞다고 생각했다.
/*
TODO: Construction with an unsigned int n as a parameter: Creates an array of n elements initialized by default.
! Tip: Try to complie int * a = new int(); then display *a.
*/
Array(unsigned int n) : _array(NULL), _arraySize(n) {
if (this->_arraySize != 0)
this->_array = new T[this->_arraySize];
}
인자가 들어오는 경우에는 new를 이용해 배열을 만들어주었고, 들어오는 인자가 0일 경우를 생각해 조건문을 달아주었다.
/*
TODO: Construction by copy and assignment operator.
! In both class, modifying either the original array or its copy after copying musn't affect the other array.
*/
Array(const Array& ref) : _array(NULL), _arraySize(ref.size()) {
if (this->_arraySize != 0)
this->_array = new T[this->_arraySize];
for (unsigned int idx = 0; idx < this->_arraySize; ++idx)
this->_array[idx] = ref[idx];
}
Array& operator=(const Array& ref) {
if (this != &ref) {
this->_arraySize = ref.size();
if (this->_arraySize != 0)
this->_array = new T[this->_arraySize];
for (unsigned int idx = 0; idx < this->_arraySize; ++idx)
this->_array[idx] = ref[idx];
}
return *this;
}
복사생성자와 할당 연산자 오버로드도 비슷하게 만들어주었다. [ ] 연산자로 요소에 접근할 수 있도록 기능을 구현할것이기 때문에, ref[idx]라는 표현을 사용했다.
/*
TODO: Element can be accessed through the subscript operator: [ ].
! When accessing an element with the [ ] operator, if its index is out of bounds, an std::exception is thrown.
*/
T& operator[](unsigned int idx) {
if (idx < 0 || idx > this->_arraySize - 1) throw OutOfBounds();
return this->_array[idx];
}
const T& operator[](unsigned int idx) const {
if (idx < 0 || idx > this->_arraySize - 1) throw OutOfBounds();
return this->_array[idx];
}
이 부분에서 [ ] 연산자를 오버로드 해주었다. 연산자 오버로드를 통해 클래스에서 바로 인덱스만으로 요소에 접근할 수 있도록 구현해줄 수 있다. 조건 중에는 잘못된 인덱스 접근일 경우 예외를 발생시키라고 되어있어서 그 부분도 작성해주었다.
/*
TODO: When accessing an element with the [ ] operator, if its index is out of bounds, an std::exception is thrown.
*/
class OutOfBounds : public std::exception {
public:
const char* what() const throw() {
return "Array - Out of range";
}
};
예외처리를 위한 클래스를 만들어주었다.
/*
* Add it if you feel necessary additional member functions.
TODO: A member function size() that returns the number of elements in the array. This member function takes no parameter and musn't modify the current instance.
*/
unsigned int size() const {
return this->_arraySize;
}
마지막으로 배열의 크기를 반환하는 함수를 만들어주었다.
느낀 점
확실히 템플릿을 사용해보니 같은 기능을 수행하는 함수들을 타입에 따라 여러개 만들어야했던 지난 과제들과 달리, 한번의 정의만으로 다양한 타입에 모두 적용할 수 있다는 것이 너무 간편했다. 다음 서클 과제인 컨테이너에서 자주 쓰인다고 하니 종종 연습해봐야겠다.
'42SEOUL > Circle5' 카테고리의 다른 글
[42Seoul] Webserv - 00 (0) | 2023.06.14 |
---|---|
[42SEOUL] CPP Module 09 (0) | 2023.04.11 |
[42SEOUL] CPP Module 08 (0) | 2023.02.18 |
[42SEOUL] CPP Module 06 [23/03/19 수정] (0) | 2023.02.18 |
[42SEOUL] CPP Module 05 (2) | 2023.02.18 |