본문 바로가기

Prog. Langs & Tools/C++

[C++] Ch12. 템플릿(Template) 프로그래밍

이번 포스팅에서는 템플릿 프로그래밍에 대해서 정리해 보려고 한다. 첨부한 이미지는 포큐 아카데미의 C++ 강의 내용 화면이다.

템플릿이란 Java나 C#에서 제네릭(generic) 메서드/클래스와 비슷하다. 컴파일 도중 모든 코드를 만들어 준다. STL 컨테이너 또한 템플릿이라고 볼 수 있다. 템플릿이 가진 첫 번째 장점은 코드를 자료형마다 중복으로 작성하지 않아도 된다는 점이며, 두 번째로는 컴파일러가 미리 코드를 만들어 주기 때문에 런타임에서 돌리면 느린 함수들을 컴파일 시에 미리 호출해서 최종 결과만 상수로 뽑아서 쓸 수가 있다.

 

함수 템플릿

예를 들어 두 수를 더하는 Add 함수가 있다고 가정해 보자. int를 더할 수도 있고, float를 더할 수도 있고, double을 더할 수도 있다.

// MyMath.h

int Add(int a, int b)
{
  return a + b;
}

float Add(float a, float b)
{
  return a + b;
}

double Add(double a, double b)
{
  return a + b;
}
// Main.cpp

#include "MyMath.h"
#include <iostream>

int main()
{
  std::cout << Add(3, 10) << std::endL;
  std::cout << Add(3.1f, 10.1f) << std::endL;
  std::cout << Add(3.1, 10.1) << std::endL;
  
  return 0;
}

만약 타입이 더 많아지면... 그 때마다 함수를 계속 만들어 주어야 하는데 과연 그렇게 꼭 해야 하는가? 여기에 대한 답으로 템플릿을 사용해 볼 수 있다.

// MyMath.h

template <typename T> // 또는 template <class T>
T Add(T a, T b)
{
  return a + b;
}
// Main.cpp

#include "MyMath.h"

int main()
{
  std::cout << Add<int>(3, 10) << std::endL;
  std::cout << Add<float>(3.1f, 10.1f) << std::endL;
  std::cout << Add<double>(3.1, 10.1) << std::endL;
  
  return 0;
}

 

클래스 템플릿

함수 뿐만이 아니라 클래스도 템플릿으로 만들어서 사용할 수 있다. int형 배열로 만들어진 클래스가 있을 때 float형도 만들어주고 싶다면 다음과 같은 방법으로 클래스 템플릿을 만들어 줄 수 있다.

// MyArray.h

#pragma once

template<typename T>
class MyArray
{
public:
  bool Add<T data>
  MyArray();

private:
  enum { MAX = 3 };
  int mSize;
  T mArray[MAX];
};
// MyArray.cpp

#include "MyArray.h"

template<typename T>
bool MyArray<T>::Add(T data)
{
  if (mSize >= MAX)
  {
    return false;
  }
  mArray[mSize++] = data;
  return true;
}

template<typename T>
MyArray<T>::MyArray() : mSize(0)
{
}
// Main.cpp

#include "MyArray.h"

int main()
{
  MyArray<int> scores;
  
  scores.Add(10); // true 반환
  scores.Add(20); // true 반환
  scores.Add(30); // true 반환
  scores.Add(40); // false 반환
  
  return 0;
}

하지만 만약 이렇게 코드를 실행하면, 에러가 발생한다. 원인은 컴파일러가 Main.cpp를 컴파일할 때, MyArray.cpp를 못 찾았기 때문이다. MyArray.h를 통해서 오직 MyArray 클래스 선언만 볼 수가 있다. 따라서 컴파일러는 MyArray<int>를 만들어 줄 수 없다. 이는 MyArray.h 헤더파일에 클래스 템플릿을 옮겨 주어서 해결할 수가 있다.

클래스 템플릿은 함수 템플릿과 달리 개체를 선언할 때 템플릿 매개변수를 반드시 명시해 주어야 한다.

MyArray<int> score; // 올바름
MyArray scores; // 에러

 

템플릿 특수화

템플릿을 일반화한 것이 다른 언어에서 제네릭이라고 불리는 것들이다. 일반화를 하는 경우 예외 상황을 처리하지 못하는 경우가 발생하고, 이러한 것들을 대응해 주기 위한 작업이 템플릿 특수화이다. 템플릿 특수화는 엄청 자주 쓰이는 내용은 아니므로 참고만 하도록 한다.

템플릿 특수화는 특정한 템플릿 매개변수를 받도록 템플릿 코드를 커스터마이즈 할 수 있다. std::vector에 좋은 예가 있다.

template <class T, class Allocator>
class std::vector<T, Allocator> {} // 모든 형을 받는 제네릭 vector

template <class Allocator>
class std::vector<bool, Allocator> {} // bool을 받도록 특수화된 vector

메모리 관리를 엄격하게 해야 하는 상황에서 4byte(=32bit)짜리 타입이면 이걸 bool(1bit)로 특수화를 했을 때 32배의 효율을 낼 수가 있다.

 

템플릿 프로그래밍의 장점과 단점은 다음과 같다

템플릿 프로그래밍의 장점

  • 컴파일러가 컴파일 도중에 각 템플릿 인스턴스에 대한 코드를 만들어 준다. 컴파일 속도는 느리지만 런타임 속도는 더 빠를 수 있음. 컨테이너 같은 곳에서 쓰기가 좋다.
  • 자료형만 다른 중복 코드를 없애는 훌륭한 방법이다. 제네릭 함수는 최대한 짧게 유지하는 것이 중요하다. 제네릭이 아니어도 되는 부분은 별도의 함수로 옮기는 것도 방법이 될 수 있다.(인라인)
  • 컴파일 도중에 다형성을 부여할 수 있다. 가상 테이블이 없어서 프로그램이 더 빠르다.

 

템플릿 프로그래밍의 단점

  • 컴파일 타임은 비교적 느리다. 그리고 템플릿 매개변수를 추가할 수록 더 느려진다.
  • 쓸모없는 템플릿 변형을 막을 방법이 없다.
  • 다형성으로 인해 파일이 커지면 느려질 수 있다. 그리고 코드를 읽기가 더 힘들어 진다.

 

참고자료

  1.  <C++ 언매니지드 프로그래밍> 포큐 아카데미