[C++] 템플렛(Template)


Study/C , C++ , MFC  2020. 3. 19. 00:55

안녕하세요. 명월입니다.


이 글은 C++에서 템플렛(Template)에 대한 글입니다.


C++에서 템플릿(Template)은 Java와 C#에서는 Generic과 비슷한 역할을 가진 기능입니다.

그러나 차이를 가지고 있는 것이라면 C++과 Java(C#)의 차이라고 볼 수 있습니다. C++에서는 컴파일 전에 처리되는 소스 레벨이 있고 컴파일 후에 처리되는 컴파일 레벨의 소스 코드가 있습니다.

이전 전처리문 분기문에서 #ifdef 등으로 컴파일 전에 소스의 컴파일 영역이 분리되는 부분이 소스 레벨 처리입니다.

링크 - [C++] 전처리문


C++의 템플릿에서는 소스 상에서는 데이터 타입을 설정하지 않고 함수나 클래스를 작성하시만, 컴파일 하기 전 단계에서 템플릿에 의해 함수가 생성되고 컴파일이 됩니다. 즉, 소스 레벨의 코드입니다.

Java나 C#은 이 소스 레벨의 코드가 없습니다. Java나 C#의 모든 데이터 타입은 Object를 상속 받습니다. 즉, Generic으로 데이터 변환을 명시한다 해도 데이터의 타입은 결국 Object 타입이고 그것에 대한 cast 에러를 줄이기 위해 데이터 타압을 체크하고 정확하게 변환을 하는게 Generic입니다.

그래서 차이는 작지만, 이것으로 인해 Java와 C#에서는 불가능한 것이 C++에서는 가능하고 C++에서 불가능한 것이 Java나 C#에서 가능한 부분이 있습니다.


함수 템플릿

템플릿이란 먼저 파라미터나 반환 값이 정해지지 않은 것입니다. C++은 무조건 pointer가 가능한 클래스 타입이 아니기 때문에 void*가 아닌 원시적 타입(primitive type)은 정확한 데이터 타입을 설정해야 합니다.

하지만 모두 소스에 작성한다면 소스가 어마어마하게 길어집니다.

#include <stdio.h>
#include <iostream>
using namespace std;
// 함수 템플릿, 데이터 타입은 설정되지 않았기 때문에 T로 예약한다.
template<typename T>
// 반환값과 파라미터가 모두 일치해야합니다.
T function(T a, T b)
{
  // 더하기
  return a + b;
}
// 실행 함수
int main()
{
  // int 형 변수 10, 20
  int a = 10;
  int b = 20;
  // function 함수의 반환값은 int형입니다.
  // 즉, 파라미터 int형, int형이면 반환 값도 int형으로 반환됩니다.
  int c = function(a, b);
  // 콘솔 출력
  cout << " int template : " << c << endl;
  
  // float 형 변수 0.1, 0.2
  float d = 0.1f;
  float e = 0.2f;
  // function 함수의 반환값은 float형입니다.
  // 즉, 파라미터 float형, float형이면 반환 값도 float형으로 반환됩니다.
  float f = function(d, e);
  // 콘솔 출력
  cout << " float template : " << f << endl;

  return 0;
}

클래스 템플릿

템플릿은 함수뿐 아니라 클래스 전체에도 설정이 가능합니다.

#pragma once
#include <stdio.h>
#include <iostream>
using namespace std;
// 클래스 템플릿
template<typename T>
class Stack
{
// 접근 제한자 (내부만)
private:
  // 인라인 클래스 (연결 리스트를 위한 클래스)
  class Node
  {
  public:
    // 데이터를 담을 포인터
    T* data;
    // 연결 포인터
    Node* pointer;
  };
  // 스택 연결 포인터
  Node* pointer = NULL;
// 접근 제한자 (내부, 상속, 외부)
public:
  // 클래스 템플릿은 해더(.h)와 소스(.cpp)가 분리가 되지 않는다.
  // 값 넣기
  void push(T* data)
  {
    // Node 클래스 할당
    Node* node = new Node();
    // 데이터를 Node 클래스에 넣는다.
    node->data = data;
    // Stack 포인터가 NULL일 경우
    if (pointer == NULL)
    {
      // 넣는다.
      pointer = node;
      return;
    }
    // NULL이 아니면 Node 포인터에 Stack 포인터를 넣는다.
    node->pointer = pointer;
    // Stack 포인터에 현 Node 포인터를 넣는다.
    pointer = node;
  }
  // 값 뺴기
  T* pop()
  {
    // Stack 포인터가 NULL이면 NULL를 반환
    if (pointer == NULL)
    {
      return NULL;
    }
    // Stack 포인터를 buffer에 남긴다.
    Node* p = pointer;
    // Stack 포인터에 Node 포인터의 다음 포인터를 넣는다.
    pointer = p->pointer;
    // Node 포인터의 값
    T* ret = p->data;
    // Node 포인터 해제
    delete p;
    // 값 리턴
    return ret;
  }
};
// 템플릿 특수화
// 특수화에 부분적으로 남기지 않는다면 cpp로 분리해서 선언이 가능하다.
// 특수화에는 template에서 왔다는 것을 명시한다.
template<>
// 클래스에 typename의 타입을 남긴다.
// int형은 원시적 타입(primitive type)이기 때문에 포인터로 받을 필요가 없어 특수화를 시켰다.
class Stack<int>
{
// 접근 제한자 (내부만)
private:
  // 인라인 클래스
  class Node
  {
  public:
    // 본 템플릿에서는 포인터이지만 int로 값을 복사한다.
    int data;
    // 연결 포인터
    Node* pointer;
  };
  // 스택 연결 포인터
  Node* pointer = NULL;
public:
  // 함수 선언
  void push(int data);
  int pop();
};
// 해더 선언
#include "Stack.h"
// 템플릿 특수화
// 파라미터를 포인터 타입이 아닌 원시형 데이터 타입
// 값 넣기
void Stack<int>::push(int data)
{
  // Node 클래스 할당
  Node* node = new Node();
  // 데이터를 Node 클래스에 넣는다.
  node->data = data;
  // Stack 포인터가 NULL일 경우
  if (pointer == NULL)
  {
    // 넣는다.
    pointer = node;
    return;
  }
  // NULL이 아니면 Node 포인터에 Stack 포인터를 넣는다.
  node->pointer = pointer;
  // Stack 포인터에 현 Node 포인터를 넣는다.
  pointer = node;
}
// 값 뺴기
int Stack<int>::pop()
{
  // Stack 포인터가 NULL이면 NULL를 반환
  if (pointer == NULL)
  {
    return NULL;
  }
  // Stack 포인터를 buffer에 남긴다.
  Node* p = pointer;
  // Stack 포인터에 Node 포인터의 다음 포인터를 넣는다.
  pointer = p->pointer;
  // Node 포인터의 값
  int ret = p->data;
  // Node 포인터 해제
  delete p;
  // 값 리턴
  return ret;
}
#include <stdio.h>
#include <iostream>
// Stack 클래스 해더 선언
#include "Stack.h"
using namespace std;
// 실행 함수
int main()
{
  // 템플릿 특수화한 int형을 선언
  Stack<int> stack1;
  // 값을 넣는다. (포인트 값이 들어가는 것이 아니다!)
  stack1.push(1);
  stack1.push(2);
  stack1.push(3);
  stack1.push(4);
  stack1.push(5);
  int result1 = 0;
  // 콘솔 출력
  while ((result1 = stack1.pop()) != NULL)
  {
    cout << " statck int : " << result1 << endl;
  }
  // 템플릿은 const char이지만, 내부에서는 const char*를 값을 받는다.
  Stack<const char> stack2;
  // 값ㅇ르 넣는다.
  stack2.push("test 1");
  stack2.push("test 2");
  stack2.push("test 3");
  stack2.push("test 4");
  stack2.push("test 5");
  const char* result2 = NULL;
  // 콘솔 출력
  while ((result2 = stack2.pop()) != NULL)
  {
    cout << " statck const char : " << result2 << endl;
  }

  return 0;
}

여기까지 C++에서 템플렛(Template)에 대한 글이었습니다.


궁금한 점이나 잘못된 점이 있으면 댓글 부탁드립니다.