본문 바로가기

무언가 만들기 위한 지식/C,C++,Embedded C

[C++] FunctionTemplate / Class Template

필자가 제대하고 처음 C++로 구성된 자료구조(Data Structure)를 볼떄, 당시 소스에는 무자비하게 거의 모든 클래스와 함수들이 template로 도배되어 있었다.

그다지 프로그래밍에 적응되지 못했던 당시, template는 나에게 Cpp를 더 복잡하게 만들어주고 소스를 지저분하게 하는 요소에 불과했지만, 지금은 이런저런 책을 접해본 후에는 template의 장점들과 유용성에 대해 알게 되었다.
(그럼에도 아직까지 그닥 많이 쓰일일은 없었다. - 그래도 이런저런 다양한 자료형 테스트 및 다양한 자료형들을 이용해야하는 상황에서는 코드의 길이를 팍 줄여 준다.)

Template란 본뜨는 공구, 형판,  주형, 보기판등을 말한다.
필자는 "틀"이라고 이해하면 편할 것이라고 생각한다. 마치 붕어빵 기계처럼 말이다.
같은 붕어빵이라도 그안에 팥을 넣으면 기본적인 붕어빵, 슈크림을 넣으면 슈크림 붕어빵, 야채를 넣으면 만두붕어빵... 이런식으로 팔듯이 말이다.

Template가 쓰이는 이유는 자신이 작성한 함수나, 클래스에서 다양한 자료형을 취급할 수 있도록 하기 위해서이다.
기본적인 기법인 Overloading을 사용해도 가능하나, 이경우 코드가 거의 두배수준으로 늘어나고 비효율적이다.

여기서 확실히 용어정의가 필요한 부분은 Function TemplateTemplate Function이다. 형용사가 앞쪽에 있다고 생각하자. 그러면 함수 템플릿은 우리가 코드상에 작성한 코드이고, 템플릿 함수는 그 코드를 통해 생성된 코드이다.


위 그림에서 봤을때, 실제로 우리가 작성하게 되는 왼쪽 코드가 바로 Function Template이다. 그리고 바로 이 Function Template인자(Argument)를 통하여 T가 정해지고 우측의 해당되는 코드로 변환되게 된다. 
이 과정은 컴파일러가 행해 주게 된다. 이 받은 인자에 의해 새로 생성된 코드가 바로 우측의 Template Function 이다.

이 템플릿 사용은 비슷하지만, 크게 함수 템플릿(function Template)와 클래스 템플릿(Class Template)로 나눌 수 있다. 
(함수에서와 클래스에서의 사용법이 조금 다르기 때문이다.

1. 먼저 함수 템플릿의 예를 보자.

[작성환경 : Window XP  작성툴 : Visual Studio 6.0   컴파일러 : Visual Studio 6.0 사용언어 : C++]


4~7 Line 기본적인 함수 템플릿 의 사용이다. typename으로 선언된 T가 인자로 받게되는 형식이다. 이 형식 T로 함수내부에서 사용되거나 리턴형식으로 사용될 수 있다.
8~12 Line에서는 인자를 여러 종류로 받아 사용할 경우 인자 순서대로 템플릿 선언을 해주면 된다.
main을 보면 function 함수 인자가, 처음에는 int, float, char, string type인데도 불구하고 템플릿을 이용한 하나의 함수로 모두 작동한 모습을 볼 수 있다.
두번째 함수 functionc2에서도 3가지 다른 인자로 각각 출력하는 모습을 확인 가능하다.

 
<결과 값은 위 와 같다. (숫자는 더해지고, char형은 아스키값으로 더한값 u, string은 더해진 값이 출력됨)>

function templete의 경우 Overloading도 가능하다. 오버로딩을 통해 특수한 인자가 올 경우, 다른 처리가 가능하다. 물론 이때는 함수 재정의를 해주어야 한다.
그 예는 다음과 같다.

[작성환경 : Window XP  작성툴 : Visual Studio 6.0   컴파일러 : Visual Studio 6.0 사용언어 : C++]


4~7 Line에는 기본적인 함수템플릿 선언이 있다. sizeof로 type의 크기를 출력하고 싶을 때이다.
이떄 string이나 char *가 들어올 경우는 전체 크기를 구하기 위해서는 서로 다른 방법을 사용하여야 한다.
이를 template specialization 이라고 한다. 함수의 오버로딩과 비슷한 개념이다.
8~14 Line에는 그 사용법에 대해 알수 있다. 처음에 template<>로 선언해준후 인자를 정해주면 해당 인자가 들어올경우 함수가 실행된다.
12 Line이 가장 정확한 template specialization의 사용방법이나, 줄여서 8~9 Line에서처럼 사용이 가능하며
처음 template<>을 선언 안해줘도 테스트 해보니 돌아가긴 한다.(Overloading)



2. 클래스 템플릿을 사용해보자.

사용법은 함수 템플릿과 흡사하나, 다른점은 class 객체 선언시 인자로들어갈 typename의 자료형을 미리 선언해주야 한다는 것이다.
그 이유로는 객체가 할당시에 가장먼저 발생하는 일이
1) 메모리 공간 할당
2) 생성자 실행

이다. 그런데 메모리 공간을 할당하려면 해당 객체의 크기를 알아야 한다. 그렇기 때문에 인자가 전달되어 생성자가 실행되기 전에 미리 자료형의 크기를 알아야 하는 것이다. 그리하여 다음예제처럼 미리 사용할 자료형의 크기를 알려준다.
테스트 코드는 차후 더블링크드 리스트에서 사용할 노드이다.~(나중에 만들것임)

[작성환경 : Window XP  작성툴 : Visual Studio 6.0   컴파일러 : Visual Studio 6.0 사용언어 : C++]


4 Line에서 함수 템플릿처럼 사용할 객체의 자료형을 정해주고, 클래스 내에서 사용할 typename T에대하여 사용한다.
중요한 부분은 main내부에서이다.
20,21,22 Line에서는 다양한 자료형으로 객체를 생성하였다.
생성하는 방법에 있어서 new를 이용한 동적할당과 생성자를 이용한 오버로딩_초기화를 이용하였다.
필자가 테스트하다가 깜빡한게 있었는데,
생성자를 Overloading하여 인자를 넣어줄 경우(사용자가 생성자 작성시) 기본생성자(Default Constructor)가 자동으로 생성되지 않는 다는 것이다. 즉 프로그래머가 직접 넣어주어야 한다.
(원래 생성자를 작성하지 않으면 자동으로 디폴트 생성자가 삽입된다.)
이걸 까먹고 프로그램을 짜다가 이런저런 낭패를 보았다 -.,-;

22 Line에서는 동적할당을 하는 부분이다. 이때 왼쪽에서 Node *선언을 해줄때에도 반드시 크기를 설정해 주어야 한다.
 Node<int> *p=new Node<int>();
처럼 왼쪽 포인터 형과 Node의 template typename을 명확히 해주어야 크기에 맞게 객체가 생성된다.


Class Template 이용하여 class를 정의하고 선언할때 또 주의해야할 점이 있다.
1. header(.h)과 cpp파일(.cpp)를 따로 분류하여 정의 및 선언해서는 안된다는 것이다.
   그 이유는, 템플릿을 처리해주는 부분은 컴파일러(compiler)이고 헤더파일과 cpp파일을 연결시켜주는 역할을 하는 것은 링커(Linker)이기 때문이다. 그래서 사용시 보통 헤더파일에서 선언과 동시에 정의를 해 주어야 한다.
2. class에서 내부함수를 정의 할때 template<typename T>를 항상 써주고 해당 객체 앞에 typename의 자료형을 확실히 해주어야 한다는 것이다. 이는 다음과 같다.

[작성환경 : Window XP  작성툴 : Visual Studio 6.0   컴파일러 : Visual Studio 6.0 사용언어 : C++]


15~26 Line을 보면 Node Class의 각 내부함수의 정의마다, template<typename T>선언을 해준 모습을 볼수 있다.
이부분을 선언하지 않으면 T는 선언되지 않았다는 compile Error을 볼 수 있을 것이다.
또한 
리턴타입  Node<T>::함수명(인자들...) 에서 클래스명 다음에 사용된 typename인 T를 써준 모습을 볼 수 있을 것이다.
위 코드는 이전 코드와 같은 결과를 출력한다.
inline으로 작성하지 않고, 선언과 정의를 분류했을 뿐이다.
위에서도 언급했지만 주의해야할 점은 template 사용시,
선언과 정의를 (.h)파일과 (.cpp)와 나눠서 하면 안된다는 것이다.