스토리텔링 개발자

[Effective C++] 48. 템플릿 메타 프로그래밍 본문

개발/Effective C++

[Effective C++] 48. 템플릿 메타 프로그래밍

김디트 2024. 7. 22. 12:55
728x90

항목 48. 템플릿 메타 프로그래밍, 하지 않겠는가?

 

 

 

템플릿 메타 프로그래밍(TMP)
  • 컴파일 도중에 실행되는 템플릿 기반의 프로그램을 작성하는 일을 말한다.
  • 템플릿 메타 프로그램은 C++ 컴파일러가 실행시키는, C++로 만들어진 프로그램이다.

 

 

 

템플릿 메타 프로그래밍(TMP)의 강점
  1. 다른 방법으로는 까다롭거나 불가능한 일을 굉장히 쉽게 할 수 있다.
  2. C++ 컴파일이 진행되는 동안에 실행되기 때문에, 기존 작업을 런타임에서 컴파일 타임으로 전환할 수 있다.

이를 통해 두 가지 이득을 취할 수 있는데,

  1. 일반적으로 프로그램 실행 도중에야 잡을 수 있었던 에러들을 컴파일 타임에 찾아낼 수 있다.
  2. TMP를 써서 만든 C++ 프로그램이 효율적일 여지가 높다.
    • 컴파일 타임에 동작을 다 해가지고 오기 때문에 실행 코드가 작아지고, 실행 시간도 짧아지며, 메모리도 적게 잡아먹는다.
    • 대신 컴파일 타임이 길어진다.

 

 

예시로 보는 TMP
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d)
{
    // typeid 연산자를 사용한다.
    if(typeid(typename std::iterator_traits<IterT>::iterator_category ==
       typeid(std::random_access_iterator_tag))
    {
        iter += d;
    }
    else
    {
        if(d >= 0) { while(d--) ++iter; }
        else { while (d++) --iter; }
    }
}
  • typeid 연산자를 쓰는 방법은 특성정보(traits)를 쓰는 방법보다 효율이 떨어진다.
    • 타입 점검 동작이 컴파일 도중이 아니라 런타임에 일어나기 때문이다.
    • 런타임 타입 점검을 수행하는 코드는 어쩔 수 없이 실행 파일에 들어가야 하기 때문이다.
  • 그렇기에 특성정보 방법을 사용한다. (항목 47 참조)
    • 이것이 바로 TMP이다.
  • typeid 방법은 성능 외에 컴파일 문제를 일으킬 수도 있다.

 

 

 

typeid의 컴파일 문제
void advance(std::list<int>::iterator& iter, int d)
{
    if(typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
       typeid(std::random_access_iterator_tag))
    {
        iter += d; // 이 코드 때문에
    }
    else
    {
        if(d >= 0) { while(d--) ++iter; }
        else { while (d++) --iter; }
    }
}

std::list<int>::iterator iter;
...
advance(iter, 10); // 이 경우 컴파일 에러가 발생한다!!
  • list<int>::iterator는 양방향 반복자이므로 iter += d의 += 연산자는 지원하지 않는다.
  • 컴파일 에러가 발생하는 이유는, 모든 소스 코드가 적법한지 검사하는 것이 컴파일러의 책무이기 때문이다.

 

 

 

튜링 완전성
  • 범용 프로그래밍 언어처럼 어떤 것이든 계산할 수 있는 능력을 갖추고 있다.
  • 변수 선언도 되고, 루프도 실행시킬 수 있고, 함수를 작성하고 호출할 수도 있다.
  • TMP는 그 자체가 튜링 완전성을 가진다.
    • 다만 보통의 C++ 구문요소들과 생김새가 많이 다르다.

 

 

 

TMP 구문요소의 생김새
  • if..else 문
    • 템플릿 및 템플릿 특수화 버전을 사용하여 처리한다.
  • 루프 문
    • 재귀(recursion)을 사용하여 루프의 효과를 낸다.
    • TMP의 루프는 재귀 함수 호출을 만들지 않고 재귀식 템플릿 인스턴스화(recursive template instantiation)을 한다.

 

 

 

컴파일을 통해 계승(factorial)을 계산하는 템플릿
template<unsigned n>
struct Factorial
{
    enum { value = n * Factorial<n-1>::value };
};

template<>
struct Factorial<0>
{
    enum { value = 1; };
};
  • 구조체 타입이 인스턴스화되도록 만들어져 있다.
  • value라는 TMP 변수가 선언되어 있다. 이는 나열자 둔갑술(enum hack)이다. (항목 2 참조)

 

 

 

C++ 프로그래밍에서 TMP가 유용한 경우
  1. 치수 단위(dimensional unit)의 정확성 확인
    1. 예를 들면 속도를 나타내는 변수에 질량을 나타내는 변수를 대입하면 에러일 것이다.
    2. TMP를 사용하면 프로그램 안에서 쓰이는 모든 치수 단위의 조합이 제대로 됐는지를 컴파일 동안에 맞춰볼 수 있다.
    3. 즉, 선행 에러 탐지(early error detection)로 사용한다.
  2. 행렬 연산의 최적화
    • typedef SquareMatrix<double, 10000> BigMatrix;
      BigMatrix m1, m2, m3, m4, m5;
      ...
      // 일반적인 C++의 방식.
      // 이 경우 네 개의 임시 행렬이 생긴다.
      // 행렬 원소들 사이에 곱셈으로 인해 네 개의 루프가 만들어진다.
      BigMatrix result = m1 * m2 * m3 * m4 * m5;
    • 표현식 템플릿(expression template)으로 개선
      • 덩치 큰 임시 객체를 없애고 루프까지 합쳐버린다.
  3. 맞춤식 디자인 패턴 구현의 생성
    • 전략, 감시자, 방문자 패턴 등의 디자인 패턴은 구현 방법이 여러 가지이다.
    • 정책 기반 설계(policy-based design)을 사용한다.
      • 따로따로 마련된 설계상의 선택(정책)을 나타내는 탬플릿을 만들어낼 수 있다.
      • 이 정책 템플릿을 임의로 조합하여 패턴을 구현할 때 사용한다.
      • 생성식 프로그래밍(generative programming)의 기초.

 

728x90
Comments