CPP Module 07
in 42Cursus on CPP Module
“CPP Module 07에 대하여”
C++ templates
이건 또 뭐지
ex00: Start with a few functions
- c++ 템플릿을 써보는게 이번 모듈의 컨셉이다. 템플릿에 대해 자세히 공부하려면
(1) 모두의 코드의, 씹어먹는 C++ - <9 - 1. 코드를 찍어내는 틀 - C++ 템플릿(template)>
(2) kks227님의 [C++ 강좌] 061 - 템플릿의 작성
를 참고해볼 것
주의 사항
- 참조 전달에 대해 자세히 공부하려면
(1) 소년 코딩, C++ 08.03 - 참조로 전달 (Pass by reference)
주의해야 할 사항이 있다. c++ 프로그래밍에 어느 정도 익숙해졌다면, 함수의 매개 변수를 참조로 전달하는 경우가 왕왕 있을 텐데 이번 모듈에서 그러한 방향성으로 함수를 구현할 경우 const 버전과, 비 const 버전을 둘 다 만들어줘야 한다.
링크를 보고 공부하거나, 따로 공부하거나 해서 참조 전달에 대해 공부했다면 참조 전달이 값 전달에 비해 복사본을 만들지 않고, 함수 내부에서 인수를 변경할 수 있다는 점에서 강점을 가진다는 것을 알 것이다. 그러나 참조 전달의 단점인 non-const 참조는 const 값 또는 r-value(리터럴, 표현식)로 초기화할 수 없기 때문에 매개 변수가 일반 변수여야 한다는 점 때문에 const 버전과 비 const 버전을 둘다 만들어줘야 한다. 이게 무슨 소리냐면…
#include "whatever.hpp"
int main( void )
{
int a = 2;
int b = 3;
::swap( a, b );
std::cout << "a = " << a << ", b = " << b << std::endl;
std::cout << "min(a, b) = " << ::min( a, b ) << std::endl;
std::cout << "max(a, b) = " << ::max( a, b ) << std::endl;
std::string c = "chaine1";
std::string d = "chaine2";
::swap(c, d);
std::cout << "c = " << c << ", d = " << d << std::endl;
std::cout << "min(c, d) = " << ::min( c, d ) << std::endl;
std::cout << "max(c, d) = " << ::max( c, d ) << std::endl;
return 0;
}
- 이건 서브젝트에 적혀있는 main문이다. swap, min, max를 참조로 전달하는 함수로 구현했다고 쳐보자.
template<typename T>
T min(T &a, T &b)
{
if (a < b)
return a;
else
return b;
}
- 간단하다. 이런식으로 swap, min, max에 대한 함수 각각 3개를 hpp 파일에 넣어줬을 것이다. 이대로 제출했다가 참조 전달에 대해 정통한 사람한테 걸리면 낭패를 본다. 저 위에 main문의 min/max 출력 부분에 다음과 같이 작성하면 어떨까?
std::cout << "min(a, b) = " << ::min( 2, 3 ) << std::endl;
- 2와 3은 r-value, 즉 숫자 리터럴이다. non-const 참조는 const 값 또는 r-value(리터럴, 표현식)로 초기화할 수 없다는 말이 무슨 의미인지 알 수 있다. 프로토타입을
T min(T &a, T &b)
으로 했다면 매개변수인T &a
,T& b
에 r-value인 숫자 리터럴을 집어넣을 수가 없다는 거다. 따로const T min(const T &a, const T &b)
를 작성해두지 않았다면 아래와 같은 컴파일 오류를 보게 된다.

꼭 참조 전달로 함수를 구현해야 하나?
- 사실 값 전달로 함수를 구현하면(
T min(T a, T b)
) 저런 고민을 할 필요가 없다. 참조로 전달한다는 것은 결국 인수의 값을 변경할 수 있다는 것을 전제하는 것인데, min이나 max는 값을 변경하는 함수가 아니기 때문이다. 괜히 min/max 같은 함수를 참조 전달 함수로 만들어서 사서 고생하느니 그냥 값 전달 함수로 만들고 설명을 잘 하는 게 편리할지도 모른다…
ex01: Iter
- 템플릿에 대해 알았다면 배열의 주소, 배열의 길이, 배열에 적용할 함수 포인터를 받는 함수 템플릿을 무리 없이 만들어낼 수 있을 거라 생각한다.
템플릿 특수화
- 본인은 main문에 다음과 같은 배열을 테스트했다.
std::string s[] = { "Java", "C++", "Python3", "Nestjs", "React" };
int i[] = { 1, 2, 3, 4, 5 };
char c[] = {'a', 'b', 'c', 'd', 'e'};
float f[] = {1.1f, 2.1f, 3.1f, 4.1f, 5.1f};
double d[] = {1.2, 2.2, 3.2, 4.2, 5.2};
- 그리고 아래의 함수를 적용했다.
template<typename T>
void next(T &str)
{
str++;
}
- 이 함수로는 int, char, float, double의 value를 각각 1씩 올려줄 수 있지만, string 배열의 값에 대해 1씩 올려주는 연산이 불가능하다(문자열에 1을 더할 수는 없다). string 배열에는 while문을 돌면서 문자열의 모든 값에 대해 1을 더해주는 함수를 따로 만들어야 한다. 이럴 때는 템플릿 특수화(Template Specialization) 기능을 써줘야 한다. 즉 아래와 같은 string 배열 전용 템플릿을 따로 만들어줘야 한다는 의미.
template<> // template specialization
void next(std::string& str)
{
for (size_t i = 0; i < str.length(); i++)
{
str[i]++;
}
}
- 이렇게 보니 오버라이딩과 비슷하다. 이렇게 특정 자료형 및 클래스(std::string)에 대해 위의 특수한 함수가 호출된다. 이렇게 하면 모든 배열들에 대해 테스트를 마칠 수가 있다.
함수 템플릿 / 템플릿 함수
💡 정리하자면,
**함수 템플릿은 함수를 만드는 데 사용되는 템플릿!**
**템플릿 함수는 템플릿을 기반으로 만들어진 함수!**
이다.
즉, 템플릿을 기반으로 만들어진, 호출이 가능한 함수임을 강조한 것이다.
**템플릿 함수는 일반 함수와 구분이 되는데, 두 가지 모두 함께 존재하기 위함이다.**
- Iter.hpp의 전체적인 내용은 함수 템플릿, 컴파일러가 이 함수 템플릿을 가지고 각 자료형에 대한 함수를 만들어서 쓴다면 그 해당 함수는 템플릿 함수라 볼 수 있겠다.
ex02: Array
왠만하면 tpp는 쓰지말고 hpp에 구현하자…
- 배열과 같은 역할을 하는 클래스를 구현한다. 템플릿으로 구현하여 여러 자료형에 대한 제네릭한 배열로 동작할 수 있다. 메모리 누수와 인덱스 초과 오류를 신경쓰면서 구현하면 될 것 같다.
개수가 0인 배열
- len이 0인 경우에 대해 처리해야 하는데, 본인의 경우 len이 0인 경우에 대해서는 별다른 처리를 하지 않았다. 사용할 일이 거의 없지만, 그렇다고 사용이 불가능하다고는 생각하지 않았기 때문에(구조체 바이트 패딩 등) throw하거나 하지는 않았다.

할당 대입 연산자
- 아무래도 동적 할당을 하다보니 처리해야할 부분이 있다.
template<typename T>
Array<T>& Array<T>::operator=(const Array& ref)
{
std::cout<<BOLDCYAN<<"Assigning Operator operating" << std::endl;
if (this != &ref)
{
if (this->array)
delete this->array;
this->len = ref.size();
this->array = new T[this->len];
for (size_t i = 0; i < this->len; i++)
{
this->array[i] = ref.array[i];
}
}
return *this;
}
if (this->array)
delete this->array;
- 새로 배열을 만들기 전에, null 체크를 하고 해제하는 과정을 생략한다면 메모리 누수가 발생할 수 있겠다.
괄호 연산자
template<typename T>
T& Array<T>::operator[](size_t i)
{
if (i >= len)
{
throw IndexOutOfBound();
}
return array[i];
}
template<typename T>
const T& Array<T>::operator[](size_t i) const
{
if (i >= len)
{
throw IndexOutOfBound();
}
return array[i];
}
- 위에서 했던 참조 전달 이야기를 다시 상기시키자면, [] 연산자는 값을 변경해서는 안되기 때문에 따로 const 자료형 참조를 반환하는 함수를 따로 만들어줘야 한다. 이 함수도 지금 와서 생각해보면 굳이 참조 전달을 해서 const와 비 const를 따로 만들어야 하나 생각이 들기는 하지만, 대체로 참조 전달을 사용하는 편이 좋고, 값이 변경되지 않아야 한다면 반드시 const를 써야 한다고 생각하기 때문에 참조 전달 함수의 사용법을 익히기 위해 위와 같이 구현하는 것이 좋다고 생각한다. 이것으로 모듈 7을 마친다.