Post List

레이블이 Effective_Cpp인 게시물을 표시합니다. 모든 게시물 표시
레이블이 Effective_Cpp인 게시물을 표시합니다. 모든 게시물 표시

2015년 1월 4일 일요일

그 밖의 이야기들

컴파일러 경고를 지나치지 말자

class B {
public:
  virtual void f() const;
};

class D : public B {
public:
  virtual void f();
};

위 Code는 warning: D:f() hides virtual B::f() 라는 경고를 유발한다. 당연하다는 듯이 넘어가는 사람들이 많은데, 지금 재선언 된것이 아니라 가려졌다는 사실을 목 놓아 외치는 중이란걸 명심해야 한다.

 * 컴파일러 경고를 쉽게 지나치지 맙시다. 여러분의 컴파일러에서 지원하는 최고 경고 수준에도 경고 메세지를 내지 않고 컴파일되는 코드를 만드는 쪽에 전력을 다 하십시오.
 * 컴파일러 경고에 너무 기대는 인생을 지양하십시오. 컴파일러마다 트집을 잡고 경고를 내는 부분들이 천차만별이기 때문입니다. 지금 코드를 다른 컴파일러로 이식하면서 여러분이 익숙해져 있는 경고 메세지가 온 데 간 데 없이 사라질 수도 있습니다.

TR1을 포함한 표준 라이브러리 구성요소와 편안한 친구가 되자.

 TR1의 라이브러리를 이루는 알맹이들은 총 14개 이다.

 - 스마트 포인터(smart pointer) : tr1::shared_ptr, tr1::weak_ptr 등...
 - tr1::function : 함수호출성 개체(callable entity)의 표현을 가능하게 해 주는 템플릿이다. 원래는 완벽히 똑같은 시그너처끼리의 호환만 가능하지만 tr1::function을 사용하면 매개변수 및 return type에 대해서도 변환이 가능한 다른 타입의 시그너처와도 호환이 가능하게 된다.
 - tr1::bind
 - 해시 테이블(hash table) : tr1::unordered_set, tr1::unordered_multiset, tr1::unordered_map, tr1::unordered_multimap
 - 정규 표현식(regular expression)
 - tr1::tuple : pair 템플릿이 신세대 버전. 2개 뿐 아니라 몇 개라도 담을 수 있다.
 - tr1::array : begin, end 등 멤버 함수를 지원하는 배열. 동적 메모리를 쓰지는 않음
 - tr1::mem_fn : 멤버 함수 포인터를 적용시키는(adapt) 용도에 쓸 수 있는, 문법적으로 천하통일을 이룬 템플릿.
 - tr1::reference_wrapper : 기존의 참조자가 객체처럼 행세할 수 있도록 만들어 주는 템플릿.
 - 난수 발생 : rand보다 몇 배는 우수한 난수 발생 기능
 - 특수 용도의 수학 함수 : 라게르(Laquerre) 다항식, 베셀(Bessel) 함수, 완전 타원 적분(complete elloptic integral) 등...
 - C99 호환성 확장 기능
 - 타입 특성정보(type trait)
 - tr1::result_of : 어떤 함수 호출의 반환 타입을 추론해 주는 템플릿

 * 최초에 상정된 표준 C++ 라이브러리의 주요 구성요소는 STL, iostream, 로케일 등입니다. 여기에는 C89의 표준 라이브러리도 포함되어 있습니다.
 * TR1이 도입되면서 추가된 것은 스마트 포인터(tr1::shared_ptr), 일반화 함수 포인터(tr1::function), 해시 기반 컨테이너, 정규 표현식 그리고 그 외의 1개 구성요소입니다.
 * TR1 자체는 단순히 명세서일 뿐입니다. TR1의 기능을 사용하기 위해서는 명세를 구현한 코드를 구해야 합니다 TR1 구현을 구할 수 잇는 자료처 중 한 군덱 바로 부스트입니다.

 부스트는 늘 여러분 가까이에

 아직 Morden C++을 사용하지 못하고 구버전의 C++ 컴파일러를 사용할 경우 많은 부분, Morden C++의 많은 부분이 부스트에 있는 경우가 많다. C++ 표준화위원들과 부스트는 밀접한 관계가 있다.

http://boost.org

 부스트 라이브러리 군단은 크게 십수개의 범주로 나뉘어 있다.

 - 문자열 및 텍스트 처리
 - 컨테이너
 - 함수 객체 및 고차(higher-order) 프로그래밍 : 람다(Lambda) 라이브러리 등...
 - 일반화 프로그래밍 : 텍사스 소때처럼 득실득실한 특성정보(traits) 클래스...
 - 템플릿 메타프로그래밍(TMP) : 컴파일 타임 단정문, 부스트 MPL 라이브러리 등...
 - 수학 및 수치 조작 : 유리수, 4원수(quaternion) 및 8원수(octonion), 최대 공약수 및 최소공배수, 난수 등
 - 정확성 유지 및 테스트
 - 자료구조
 - 타 언어와의 연동 지원 : C++ 과 Python 사이의 걸림돌 없는 상호운영을 가능하게 해줌
 - 메모리 : Pool 라이브러리 등
 - 기타 : CRC 점검, 날짜 및 시간 조작, 파일 시스템 횡단 등을 지원하는 라이브러리 등

 * 부스트는 동료 심사를 거쳐 등록되고 무료로 배포되는 오픈 소스 C++ 라이브러리를 개발하는 모임이자 웹사이트입니다. 또한 C++ 표준화에 있어서 영향력을 있는 역할을 맡고 있습니다.
 * 부스트에서 배포되는 라이브러리들 중엔 TR1 구성요소에 들어간 것도 잇지만, 그 외에 다른 라이브러리들도 아주 많습니다.



위치지정 new를 작성한다면 위치지정 delete도 같이 준비하자.

 위치지정(placement) new 란 operator new의 기본형과 달리 매개변수를 추가로 받는 형태를 말한다. 개념적으로는 그냥 추가 매개변수를 받는 new 라고 이해하면 된다. 추가매개변수 new의 원조가 위치지정 매개변수를 추가로 받는 것이어서 그냥 그렇게 불린다.

 Widget *pw = new Widget;

 위의 문장은 두 개의 함수가 호출된다. 우선 메모리 할당을 위한 operator new가 호출되고, 그 뒤에 Widget이 기본 생성자가 호출된다. 첫 번째 함수 호출이 무사히 지나갔는데 두 번째 함수 호출이 진행되다가 예외가 발생했을 경우, 첫 단계에서 미이 끝난 메모리 할당을 어떻게 해서든 취소해야 한다. 이 때 매개변수의 개수 및 타입이 똑같은 버전의 operator delete가 호출된다.

 new, delete의 기본형을 사용하고 있었다면 그다지 대수로운 사안이 아니지만 위치지정 new를 사용한 상황에서 같은 형식의 delete가 없다면 new로 저질러버린 메모리 할당을 어떻게 되돌려야 할지 갈팡질팡할 수밖에 없다. 결국은 아무것도 하지 않고 메모리 누수가 발생하게 된다.

 위치지정 delete가 호출되는 경우는 위치지정 new의 호출에 묻어서 함께 호출되는 생성자에서 예외가 발생할 때뿐이다. 정상적으로 delete를 실행 할 경우에는 절대로 위치지정 delete를 호출하는 쪽으로 가지 않는다.

 단, 실수로 빼먹지 말아야 하는 부분이 있다. 이름만 같아도 바깥쪽 유효범위의 모든 함수, 변수, 타입 등을 가려버린다는 사실을 다들 알 것이다.

void* operator new(std::size_t) throw(std::bad_alloc); // 기본형
void* operator new(std::size_t size, void*) throw(); // 위치지정
void* operator new(std::size_t, const std::nothrow_t&) throw(); // 예외불가

 C++가 전역 유효 범위에서 제공하는 operator new의 형태는 다음의 세 가지가 표준이라는 점을 기억해두고, 어떤 형태이든 간에 이 함수들을 가리지 말아야 한다. 이것을 쉽게 해결 할 수 있는 방법이 하나 있다. 기본 클래스에 new, delete의 기본 형태를 전부 넣어두고, 파생 클래스에서는 상속과 using 선언을 2단 콤보로 사용해서 표준 형태를 전부 끌어와 외부에서 사용할 수 있게 만든 후, 원하는 사용자 정의 형태를 선언해 주면 된다.

class StandardNewDeleteForms {
public:
  // 기본형
  static void* operator new(std::size_t size) throw(std::bad_alloc)
  { return ::operator new(size); }
  static void operator delete(void *pMemory) throw()
  { ::operator delete(pMemory); }

  // 위치지정
  static void* operator new(std::size_t size, void* ptr) throw()
  { return ::operator(size, ptr); }
  static void operator delete(void* pMemory, void *ptr) throw()
  { ::operator delete(pMemory, ptr); }

  // 예외불가
  static void* operator new(std::size_t size, const std::nothrow_t& nt) throw();
  { return ::operator new(size, nt); }
  static void operator delete(void* pMemory, vonst std::nothrow_t& nt) throw()
  { ::operator delete(pMemory, nt); }
};

class Widget : public StandardNewDeleteForms {
public:
  using StandardNewDeleteForms::operator new;
  using StandardNewDeleteForms::operator delete;

  static void* operator new(std::sie_t size, std::ostream& logStream)
    throw(std::bad_alloc);
  static void operator delete(void* pMemory, std::ostream& logStream) throw();
  ...
};

 * operator new 함수의 위치지정(placement) 버전을 만들 때는, 이 함수와 짝을 이루는 위치지정 버전의 operator delete 함수도 꼭 만들어 주세요. 이 일을 빼먹었다가는, 찾아내기도 힘들며 또 생겼다가 안 생겼다 하는 메모리 누출 현상을 경험하게 됩니다.

 * new 및 delete의 위치지정 버전을 선언할 때는, 의도한 바도 아닌데 이들의 표준 버전이 가려지는 일이 생기지 않도록 주의해 주세요.



new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자.

void * operator new(std::size_t size) throw(std::bad_alloc)
{
  using namespace std;

  if (size == 0) { size = 1; } // 0바이트 요청에는 1로 간주하고 처리

  while (true) {
    메모리 할당;
    if (할당 성공) return (할당 메모리의 포인터);
    // 할당 실패 -> 현재 new-hander를 찾음
    new_handler globalHandler = set_new_handler(0);
    set_new_handler(globalHandler);

    if (globalHandler) (*globalHandler)();
    else throw std::bad_alloc();
  }
}

void operator delete(void* rawMemory) throw() {
  if (rawMemory == 0) return;
  메모리 해제;
}

 - 반환값이 제대로 되어 있어야 한다.
 - 0바이트가 요구되었을 때조차도 적법한 포인터를 반환해야 한다.
 - 다중스레드 환경에서 사용되는 Code라면 스레드 안전성이 보장되어야 한다. 단일 스레드라면 스레드 안전성을 신경 안쓰는 것이 휠씬 더 빠르다.
 - 무한 루프 안에서 구현을 하며 이 루프를 빠져나오는 유일한 조건은 메모리 할당에 성공하는 것이다.
 - delete 작성시 널 포인터에 대해서 안전성을 보장하라.

 특정 클래스 전용 할당자를 만들때 주의해야 할 것은 그 파생 클래스에 대해서 정상동작 하지 않을 수 있다.

class Base {
public:
  static void * operator new(std::size_t size) throw(std::bad_alloc);
  ...
};

class Derived : public Base { ... };

Derived *p = new Derived; // Base::operator new가 호출 됨 !

 가장 좋은 해결 방법으로는 "틀린" 메모리 크기가 들어왔을 때는 시작부분에서 확인한 후에 표준 operator new를 호출하는 쪽으로 살짝 비켜가게 만들어 주는 것으로 해결이 된다. delete의 경우도 마찬가지다.

class Base {
public:
  static void * operator new(std::size_t size) throw(std::bad_alloc);
  static void operator delete(void *rawMemory, std::size_t size) throw();
  ...
};

void * Base::operator new(std::size_t size) throw(std::bad_alloc) {
  if (size != sizeof(Base)) return ::operator new(size);
  ...
}

void Base::operator delete(void* rawMemory, std::size_t size) throw() {
  if (rawMemory == 0) return;
  if (size != sizeof(Base)) {
    ::operator delete(rawMemory);
    return;
  }
  ...
  return;
}

 * 관례적으로, operator new 함수는 메모리 할당을 반복해서 시도하는 무한 루프를 가져야 하고 메모리 할당 요구를 만족시킬 수 없을 때 new-handle를 호출해야 하며, 0바이트에 대한 대책도 있어야 합니다. 클래스 전용 버전은 자신이 할당하기로 예정된 크기보다 더 큰(틀린) 메모리 블록에 대한 요구도 처리해야 합니다.

 * operator delete 함수는 널 포인터가 들어왔을 때 아무 일도 하지 않아야 합니다. 클래스 전용 버전의 경우에는 예정 크기보다 더 큰 블록을 처리해야 합니다.

new 및 delete를 언제 바꿔야 좋은 소리를 들을지를 파악해 두자.

 C++가 제공해주는 new/delete는 지극히 대중적이고 모든 사용자에게 욕먹지 않게끔 적당히 무난하게 동작한다. 하지만 어느 누구에게도 칭찬 들을 정도록 훌륭하진 못하다. 그렇기 때문에 개발자가 자신의 프로그램이 동적 메모리를 어떤 성향으로 사용하는지를 제대로 이해하고 있다면, 직접 만들어 쓰는 편이 월씬 더 우수한 성능을 낼 확률이 높다. 간혹 실행속도에서 10의 몇 제곱 배 단위로 빨라진다던지 메모리를 50%나 덜 차지하는 등의 성능 향상을 발휘하도록 개선하는 것이 어렵지 않게 구현된다.

 버퍼 오버런 및 언더런을 탐지하기 쉬운 형태로 만들어 주는 전역 operator new를 후다닥 만들어 봤다.

static const int SIGNATURE = 0xDEADBEEF;
typedef unsigned char BYTE;

void* operator new(std::size_t size) throw(std:bad_alloc) {
  using namespace std;
  size_t realSize = size + 2 * sizeof(int); // 경계표시 2개 추가

  void *pMem = malloc(realSize);
  if (!pMem) throw bad_alloc();

  // 경계표시
  *(static_cast<int*>(pMem)) = SIGNATURE;
  *(reinterpret_cast<int*>(static_cast<BYTE*>(pMem) + realSize - sizeof(int)))
    = SIGNATURE;

  return static_cast<BYTE*>(pMem) + sizeof(int); // 경계표시 바로 다음 메모리를 반환
}

 위 Code는 좀 까다로운 문제들을 무시했는데 그중 하나가 바이트 정렬(alignment) 이다. 많은 컴퓨터의 경우 아티켁처적으로 특정 타입의 데이터가 특정 종류의 메모리 주소를 시작 주소로 하여 저장될 것을 요구하는 경우가 많다. 예를 들어 포인터는 4의 배수에 해당되는 주소에 맞추어 저장되어야 하며 double은 8의 배수에 해당하는 주소에 맞추어 저장되어야 한다는 것이다. 이걸 지키지 않은 경우 프로그램이 다운 될 수도 있고, 정말 다행스러울 경우 실행속도가 엄청나게 느려지는 것으로 마무리 될수도 있다.

 new/delete 를 직접 구현하는 것이 생각만큼 쉽지는 않다. 꼭 만들어 쓸 경우가 없다면 굳이 들이댈 필요는 없다. 농담이 아니라 진짜 필요가 없다. 메모리 관리 함수만 전문적으로 다루는 상업용 제품들도 많이 있다.링크 한 번 다시 하는 것만으로 개선된 기능과 향상된 성능을 맛 볼 수 있다는 거다. [물론 실탄(돈)이 있어야 겠지만]

 그럼 언제 new/delete를 다른 것으로 대체하는 작업을 해야 의미가 있겠는가 ?

 - 잘못된 합 사용을 탐지하기 위해 : 버퍼 오버런, 언더런 (할당된 메모리 블록 앞이나 뒤를 넘어서 기록하는 것) 탐지
 - 동적 할당 메모리의 실제 사용에 관한 Log를 수집하기 위해
 - 할당 및 해제 속력을 높이기 위해 : 부스트의 Pool 라이브러리 같이 고정된 크기의 객체만 만들어주는 등...
 - 기본 메모리 관리자의 공간 오버헤드를 줄이기 위해
 - 적당히 타협한 기본 할당자의 바이트 정렬 동작을 보장하기 위해 : 바이트 정렬을 보장해주지 않는 Compiler 사용시
 - 임의의 관계를 맺고 있는 객체들을 한 군데에 나란히 모아 놓기 위해 : 페이지 부제(page fault) 발생 횟수 최소화
 - 그때그때 원하는 동작을 수행하도록 하기 위해 : 보안 강화를 위해 delete한 메모리를 0으로 덮어쓰기 등...

 * 개발자가 스스로 사용자 정의 new 및 delete 를 작성하는 데는 여러 가지 나름대로 타당한 이유가 있습니다. 여기에는 수행 성능을 향상시키려는 목적, 힙 사용 시의 에러를 디버깅하려는 목적, 힙 사용 정보를 수집하려는 목적 등이 포함됩니다.
 

new-handler의 동작 원리를 제대로 이해하자.

 new 함수로 메모리 할당에 실패한 경우 예외를 던지게 되어 있습니다. 이 에러 처리 함수를 가리켜 new 처리자(new-handler, 할당에러 처리자)라고 한다.

namespace std {
  typedef void (*new_handler)();
  new_handler set_new_handler(new_handler p) throw();
}

void outOfMem() {
  std::cerr << "Unable to satisfy request for memory\n";
  std::abort();
}

int main() {
  std::set_new_handler(outOfMem);
  int *pBifDataArrat = new int[100000000L];
  ...
}

 new-handler가 어떤 것인지 대충 알아볼수 있게 해주는 예시이다. 하지만 이것도 완벽하진 않다. new-handler애서 문자열을 또 할당해야하는 Code가 있는데 만약 저기서 또 에러가 발생한다면 무한루프에 빠질수도...

 new-handler는 다음 동작 중 하나를 꼭 해주어야 한다.

 - 사용할 수 있는 메모리를 더 많이 확보 : ex)시작시 메모리 블록을 크게 하나 할당해 놓았다가 여기서 그 메모리를 사용할 수 있게 허용하는 등...
 - 다른 new 처리자를 설치 : 현재 new-handler 안에서 set_new_handler를 호출
 - new-handler의 설치를 제거 : set_new_handler(NULL); 이러면 예외를 던지게 됨
 - 예외를 던짐 : bad_alloc 나 거기서 파생된 예외를 던짐
 - 복귀하지 않음 : abort 또는 exit 호출

 특정 class만을 위한 new-handler를 두고 싶다면, 직접 구현해야 한다. class 내에 new-handler 타입의 정적 멤버 데이터를 선언해서 거기에 보관해두고 필요할 때 set_new_hander로 선언을 해두며, 처리가 끝난 뒤 원래 new-handler로 다시 돌려주면 된다. new 구현에는 new-handler이 자원관리를 위한 RAII class를 이용하였다.

class NewHandlerHolder {
public:
  explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}
  ~NewHandlerHolder() { std::set_new_handler(handler); }
private:
  std::new_handler handler;

  NewHandlerHolder(const NewHandler&);
  NewHandlerHolder& operator= (const NewHandlerHolder&);
};

class Widget {
public:
  static std::new_handler set_new_handler(std::new_handler p) throw() {
    std::new_hander oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
  }
  static void* operator new(std::size_t size) throw(std::bad_alloc) {
    NewHandlerHolder h(std::set_new_handler(currentHandler));
    return ::operator new(size);
  }
private:
  static std::new_handler currentHandler; // 클래스 구현 파일에서 NULL로 초기화 필요
};

 잠시만 생각해 보면, 이런 Code는 어떤 클래스를 쓰더라도 똑같이 나올 것 같다. 이런 용도에 손쉽게 쓸 수 있는 손질 방법으로는 믹스인(mixin) 양식의 기본 클래스를 추천하고 싶다. 템플릿으로 구현하여 각 클래스 별로 만들어지긴한데 그 안 어디에도 템플릿타입에 대한 Code는 없다. 이런 형식을 신기하게 반복되는 템플릿 패턴(curiously recurring template pattern: CRTP)라고 부른다.

template<typename T>
class NewHandlerSupport {
public:
  static std::new_handler set_new_handler(std::new_handler p) throw();
  static void* operator new(std::size_t size) throw(std::bad_alloc);
  ...
private:
  static std::new_handler currentHandler;
};

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw() {
  std::new_handler old = currentHandler;
  currentHandler = p;
  return old;
}

template<typename T>
void* NewHandlerSupprot<T>::operator new(std::size_t size) throw(std::bad_alloc) {
  NewHandlerHolder h(std::new_handler(currentHandler));
  return ::operator new(size);
}

template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler = 0;

class Widget : public NewHandlerSupport<Widget> { ... };

 * set_new_handler 함수를 쓰면 메모리 할당 요청이 만족되지 못핼을 때 호출되는 함수를 지정할 수 있습니다.




 * 예외불가(nothrow) new는 영향력이 제한되어 있습니다. 메모리 할당 자체에만 적용되기 때문입니다. 이후에 호출되는 생성자에서는 얼마든지 예외를 던질 수 있습니다.

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

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

 TMP에는 엄청난 강점이 있다. 첫째, 다른 방법으로는 까다롭거나 불가능한 일을 굉장히 쉽게 할 수 있다. 둘째, C++ 컴파일이 진행되는 동안 실행되기 때문에, 런타임 영역에서 컴파일 타임 영역으로 작업 전환할 수 있다. 즉 프로그램 실행 도중에 잡혀 왔던 몇몇 에러들이 컴파일 도중에 찾을 수 있게 되었다. 또 실행 코드가 작아지고, 시간도 짧아지고, 메모리도 적게 잡아먹게 되었다.

 TMP의 동작 원리를 엿볼 수 있는 방법 중 가장 대표적인 것이 루프이다. TMP의 루프는 재귀 함수 호출을 만들지 않고 재귀식 템플릿 인스턴스화(recursive template instanitation)을 한다. 일반 프로그래밍의 시작은 "hello world" 이듯, TMP의 시작은 계승(Factorial)을 계산하는 템플릿이다.

template<unsigned n>
struct Factorial { enum { value = n * Factorial<n-1>::value }; };

template<>
struct Factorial<0> { enum { value = 1}; };

int main() {
  std::cout << Factorial<5>::value;
  std::cout << Factorial<10>::value;
}

 Code가 깔끔하게 이쁘지 않나 ? 더군다나 놀라운 사실은 위의 연산을 런타임에 하는게 아니라 컴파일 타임에 C++이 미리 다 해놓는다는 것이다. 그 와중 오류가 있다면 컴파일 오류로 남긴다.

 TMP가 실력 발휘하는 분야의 세가지 예를 들어보겠다.

 1. 치수 단위(dimensional unit)의 정확성 확인) : 과학기술 분야의 응용프로그램을 만들 때는 중요한 부분이다. 프로그램 안의 모든 치수 단위의 조합이 제대로 맞춰졌는지 컴파일 타임에 볼 수 있다.

 2. 행렬(Matrix) 연산의 최적화 : 행열에서 operator* 등의 연산은 큰 임시 행렬이 생겨야 하며 루프연산을 순차적으로 반복해야 한다. 표현식 템플릿(expression template)를 사용하면 덩치 큰 임시 객체를 없애는 것은 물론이고 루프까지 합쳐 버릴 수 있다. 이로서 메모리도 적게 먹으며 빛처럼 빠른 소프트웨어를 만들 수 있다.

 3. 맞춤식 디자인 패턴 구현의 생성 : 전략(Strategy) 패턴, 감시자(Observer) 패턴, 방문자(Visitor) 패턴 등 디자인 패턴 구현방법이 여러가지 일 경우 정책 기반 설계(policy-based design)을 사용하면 따로따로 마련된 설계상의 선택[ 보통 정책(policy)라고 부름 ] 을 나타내는 템플릿을 만들수 있다. 이렇게 만들어진 정책 템플릿은 서로 임의대로 조합되어 수백가지 타입 생성이 가능해진다. 이른바 생성식 프로그래밍(generative programming)의 기초가 되는 기술이다.

 하지만 TMP는 문법이 비직관적이고, 개발도구의 지원도 아주 미약하다. 즉 프로그래머 입장에서 아주 어렵다. ㅠㅠ

 * 템플릿 메타프로그래밍은 기존 작업을 런타임에서 컴파일 타임으러 전환하는 효과를 냅니다. 따라서 TMP를 쓰면 선행 에러 탐지와 높은 런타임 효율을 손에 거머쥘 수 있습니다.

 * TMP는 정채 선택의 조합에 기반하여 사용자 정의 코드를 생성하는데 쓸 수 있으며, 또한 특정 타입에 대해 부적절한 코드가 만들어지는 것을 막는 데도 쓸 수 있습니다.

타입에 대한 정보가 필요하다면 특성정보 클래스를 사용하자.

 STL은 기본적으로 컨테이너(container), 반복자(iterator), 알고리즘(algorithm), 유틸리티(utility) 들로 구성되어 있다.

 STL 반복자는 지원하는 연산에 따라 다섯 개의 범주로 나뉜다.

 1. 입력 반복자(input iterator) : 전진만 가능, 한칸씩 이동, 현재위치 읽기만 가능하고 한번만 가능. ex) istream_iterator
 2. 출력 반복자(output iterator) : 입력 반복자와 비슷하지만 출력만 가능. ex) ostream_iterator
 3. 순방향 반복자(forward iterator) : 입력 반복자 + 출력 반복자, 현재 위치 읽기 쓰기를 동시에 여러번 가능. 다중 패스(multi-pass) 알고리즘 문제에 쓸 수 있음. ex) TR1의 해시 컨터이너 반복자
 4. 양방향 반복자(bidirectional iterator) : 순방향 반복자 + 뒤로 가는 기능 추가 ex) STL의 list, set, multiset, map, multimap
 5. 임의 접근 반복자(random access iterator) : 양방향 반복자 + 반복자 산술 연산(iterator arithmetic) 수행 기능 추가. ex) vector, deque, string

 advance 라는 이름의 지정된 반복자를 지정된 거리 만큼 이동시키는 템플릿을 구현 할 경우 위 5가지 모두에 대해서 순차적으로 이동하게 구현을 한다면 임의 접근 반복자의 경우 분명 손해를 본다.

 이럴 경우 특성정보(traits)를 구현하면 해결 방법이 생긴다. 특성정보란 컴파일 도중에 어떤 주어진 타입의 정보를 얻을 수 있게하는 객체를 지칭하는 개념이다. C++에 미리 정의된 구조가 아니라 C++ 프로그래머들이 따르는 구현기법이다. 여기에는 몇 가지 요구사항이 있다. 그중 가장 중요한 하나가 기본제공 타입과 사용자 정의 타입에서 모두 돌아가야 한다. 즉 포인터 등의 기본제공 타입에 적용할 수 있어야 한다는 것이다. 그런데 포인터나 int등의 기본타입은 특성정보를 그 타입 내부에 가지고 있을 수 없다. 즉 특성정보는 그 타입의 외부에 존재해야 한다. 가장 표준적인 방법은 특성정보를 템플릿 및 그 템플릿의 1개 이상의 특수화 버전으로 구현하는 것이다. 그리고 보통 struct 로 구현한다. 그 구조체를 가리켜 '특성정보 클레스' 라고 부른다.

 STL 반복자는 아래와 같이 구현되어 있다.

struct input_itertor_tag {};
struct output_iterator_tag {};
struct forward_iterator_tag : public input_iterator_tag {};
struct bidirectioanl_iterator_tag : public forward_iterator_tag {};
struct random_access_iterator_tag : public bidirectional_iterator_tag {};

template< ... >
class deque {
public:
  class iterator {
  public:
    typedef random_access_iterator_tag iterator_category;
  ...
};

template< ... >
class list {
...
    typedef bidirectional_iterator_tag iterator_category;
...
};

 클래스 내부 typedef를 앵무새처럼 똑같이 재생한 것이 iterator_trait 이다. 포인터 타입에 대해서는 부분 템플릿 특수화(partial template specialization) 버전을 제공한다.

template<typename T>
struct iterator_traits {
  typedef typename T::iterator_category iterator_category;
  ...
};

template<typename T>
struct iterator_traits<T*>
{
  typedef random_access_iterator_tag iterator_category;
  ...
};

 특성정보 클래스의 설계 및 구현 방법은 다음과 같이 정리 할 수 있다.

 - 다른 사람이 사용하도록 열어 주고 싶은 타입 관련 정보를 확인한다. ( 반복자 범주 )
 - 그 정보를 식별하기 위한 이름을 선택한다. ( iterator_category )
 - 지원하고자 하는 타입 관련 정보를 담은 템플릿 및 그 템플릿의 특수화 버전을 제공

 이제 advance의 의사코드를 다음과 같이 구현하면 될 것이다.

template<typename T, typename D>
void advance(T& t, D d) {
  if (typeid(typename std::iterator_traits<T>::iterator_category)
    == typeid(std::random_access_iterator_tag))
  ...
}

 하지만 위와 같이 구현하면 컴파일 도중에 할 수 있는 것을 굳이 실행 도중에 하도록 되기 때문에 진정한 시간 낭비가 아닐 수 없을 뿐더러 실행 코드의 크기도 비대해진다. 이것을 개선 할 수 있는 방법은 바로 오버로딩이다. 즉 매개변수는 다르지만 이름이 같은 함수를 이용하는 것이다.

template<typename T, typename D>
void doAdvance(T& t, D d, std::random_access_itertor_tag) {
  t += d;
}

template<typename T, typename D>
void doAdvance(T& t, D d, std::bidirectional_itertor_tag) {
  if (d >= 0) { while (d--) ++t; }
  else ( while (d++) --t; }
}

template<typename T, typename D>
void doAdvance(T& t, D d, std::input_itertor_tag) {
  if (d < 0) { throw std::out_of_range("Negatice distance"); }
  while (d--) ++t;
}

template<typename T, typename D>
void advance(T& t, D d) {
  doAdvance(t, d, typename std::iterator_traits<T>::iterator_category);
}

 특성정보 클래스를 어떻게 사용하는지 깔끔하게 정리해 보겠다.

 - 작업​자(worker) 역할을 마을 함수 혹은 함수 템플릿( doAdvance )을 특성정보 매개변수를 다르게 하여 오버로딩하고, 각 특성에 맞추어 구현한다.
 - 작업자를 호출하는 주인(master) 역할을 맡을 함수 혹은 함수 템플릿( advence )을 만들고 특성정보 클래스에서 제공하는 정보를 넘겨서 작업자를 호출한다.

 C++ 표준 라이브러리에 특성정보는 엄청 많다. 그중 대표적인게 value_type, char_traits, numeric_limits 등이 있다. TR1에는 is_fundamental<T>, is_array<T>, is_base_of<T1, T2> 등 50여가지가 있다.

 * 특성정보 클래스는 컴파일 도중에 사용할 수 있는 타입 관련 정보를 만들어냅니다. 또한 특성정보 클래스는 템플릿 및 템플릿 특수 버전을 사용하여 구현합니다.




 * 함수 오버로딩 기법과 결합하여 특성정보 클래스를 사용하면, 컴파일 타임에 결정되는 타입별 if ... else 점검문을 구사할 수 있습니다.

타입 변환이 바람직할 경우에는 비멤버 함수를 클래스 템플릿 안에 정의해 두자.

 모든 매개변수에 대해 암시적 타입 변환이 되도록 만들기 위해서는 비멤버 함수밖에 방법이 없다. 이것을 템플릿에 적용되게 하면 에러가 발생한다.

template<typename T>
class Rational {
public:
  Rational(const T& numerator = 0, const T& denominator = 1);
  const T numerator() const;
  const T denominator() const;
  ...
};

template<typename T>
const Rational<T> operator* (const Rational<T>& left, const Rational<T>& right)
{ ... }

Rational<int> oneHalf(1,2);
Rational<int> result = oneHalf * 2;

 위의 Code는 에러가 발생한다. operator* 를 호출하기 위해서 대관절 T가 무언인지 알아야 한다. oneHlaf 쪽은 공략이 쉽다. Rational<int> 타입이라서 T 가 int라는 것을 바로 알 수 있다. 문제는 2에 있다. 2는 int형이다. Rational<int>에는 explicit로 선언되지 않은 생성자가 있으니깐 Rational<int>로 변환이 될 것이라고 예상되겠지만, 컴파일러는 그렇게 동작하지 못한다. 왜냐면 템플릿 인자 추론(template argument deduction) 과정에서는 암시적 타입 변환이 고려되지 않기 때문이다. 절대로 안된다. 이처럼 힘든 처지에서 템플릿 인자 추론을 해야 하는 수고로부터 컴파일러를 해방시킬 수 있는 방법이 있다. 클래스 템플릿 안에 프렌드 함수를 넣어 두면 함수 템플릿으로서의 성격을 주지 않고 특정 함수 하나를 나타낼 수 있다는 사실을 이용하면 된다.

template<typename T>
class Rational {
public:
  ...
  friend const Rational operator*(const Rational& left, const Rational& right); // 선언
};

template<typename T>
const Rational<T> operator* (const Rational<T>& left, const Rational<T>& right)
{ ... } // 위에 선언된 friend 함수에 대한 정의

 하지만 이 Code는 컴파일은 되지만, 링크가 안된다. 간단하게 해결 하려면 함수의 본문 (정의) 부분을 선언부와 붙이면 된다.

template<typename T>
class Rational {
public:
  ...
  friend const Rational operator*(const Rational& left, const Rational& right) {
    return Rational(left.numerator() * right.numerator(),
      left.denominator() * right.denomintor());
  }
};

 이 경우는 함수가 무척 짧지마 만약 긴 경우는 어떻게 해야 할까 ? 이 경우는 "프렌드 함수는 도우미만 호출하게 만들기" 방법을 이용하면 된다.

template<typename T> class Rational;

template<typename T>
const Rational<T> Multiply(const Rational<T>& left, const const Rational<T>& right);

template<typename T>
class Rational {
public:
  ...
  friend const Rational operator*(const Rational& left, const Rational& right) {
    return Multiply(left, right);
  }
};

template<typename T>
const Rational<T> Multiply(const Rational<T>& left, const const Rational<T>& right) {
  return Rational<T>(left.numerator() * right.numerator(),
    left.denominator() * right.denominator());
}

 * 모든 매개변수에 대해서 암시적 타입 변환을 지원하는 템플릿과 관계가 있는 함수를 제공하는 클래스 템플릿을 만들려고 한다면, 이런 함수는 클레스 템플릿 안에 프랜드 함수로서 정의합시다.




"호환되는 모든 타입"을 받아들이는 데는 멤버 함수 템플릿이 직방!

 스마트 포인터(smart pointer)는 포인터처럼 동작하면서도 포인터가 주지 못하는 상콤한 기능을 덤으로 갖고 있다. auto_ptr, tr1::shared_ptr, STL 컨테이너의 iterator 등이 있다..
 반면 일반 포인터도 스마트 포인터로 대신할 수 없는 특징이 있다. 그 중 하나가 암시적 변환(implicit conversion)을 지원 한다는 점이다. 파생 클래스는 기본 클래스로 변환되고, 비상수 객체에서 상수 객체로의 암시적 변환이 가능하다. 스마트 포인터는 이것이 안된다.

 그럼 어떻게 방법이 없을까 ? 멤버 함수 템플릿(member function template)를 사용하면 된다. 바로 생성자를 만들어내는 템플릿을 사용하는 것이다.

template<typename T>
class SmartPtr {
public:
  template<typename U>
  SmartPtr(const SmartPtr<U>& other) : heldPtr(other.get()) { ... }
  T* get() const { return heldPtr; }
private:
  T* heldPtr;
};

 이런 꼴이 같은 템플릿을 써서 인스턴스화 되지만 타입이 다른 타입의 객체로 부터 원하는 객체를 만들어주는 것을 일반화 복사 생성자(generalized copy constructor)라고 부른다. 그렇다고 해서 기본 클래스로부터 자식 클래스로 변환하거나 int* 를 double* 로 변환하는 등의 원래부터 안되는 것을 되게 해주지는 못한다. 못하게 하는것이 세상의 이치에도 맞는 것이고.

 멤버 함수 템플릿은 복사생성자 뿐 아니라 대입 연산에도 많이 쓰인다.

 * 호환되는 모든 타입을 받아들이는 멤버 함수를 만들려면 멤버 함수 템플릿을 사용합시다.

 * 일반화된 복사 생성 연산과 일반화된 대입 연산을 위해 멤버 템플릿을 선언했다 하더라도, 보통의 복사 생성자와 복사 대입 연산자는 여전히 직접 선언해야 합니다.



매개변수에 독립적인 코드는 템플릿으로부터 분리시키자.

 아무 생각없이 템플릿을 사용하면 템플릿의 적, 코드 비대화(code bloat)가 초래될 수 있다. 똑같은 내용의 코드가 여러 벌로 중복되어 파일로 구워진다는 뜻이다. Code 자체만 보면 깔끔하지만, 이진 코드가 템플릿으로 인해 불어터지는 불상사가 일어난다는 얘기다.

 공통성 및 가변성 분석(commonality and variabiity analysis) 의 관점에서 살펴봐야 한다.

 어떤 함수를 만들고 있다가 무심코 다른 함수를 봤는데, Code의 일부가 비슷하면 어떻게 하나 ? Copy & Paste ? 당연히 이렇게 하다면 비오는 날 먼지날때까지 맞아야겠고... 공통 Code를 별도의 함수로 만들고 이 함수를 기존의 두 함수에서 호출하도록 수정하는게 고치야지. 당연히 이렇게 다들 할테고...
 클래스를 만드는데 다른 클레스에서 공통된 부분을 발견한다면 ? 당연히 공통 부분을 새로운 클래스에 옮긴 뒤, 원래 2개의 클래스에서 이 클래스를 상속받거나 객체 합성을 사용하도록 고쳐야한다.
 템플릿을 작성할때도 똑같은 분석을 하고 똑같은 방법으로 Code 중복을 막으면 된다. 하지만 우리의 뒷통수를 노리는 뜻밖의 전개가 하나 있다. 템플릿이 아닌 Code는 코드 중복이 명시적이다. 즉 눈에 보인다. 하지만 템플릿의 경우는 암시적이다. 피나는 수련을 통하여 감각적으로 알아채야 한다는 것이다.

template<typename T, std::size_t n>
class SquareMatric {
public:
  void invert();
};

SquareMatrix<double, 5> m1;
m1.invert();

SquareMatrix<double, 10> m2;
m2.invert();

 위의 Code를 보면 m1과 m2의 invert()가 각각 다른 함수를 호출한다는 것을 다들 알 것이다. 당연 같은 함수일 수는 없다. 하지만 행과 열의 크기를 나타내는 상수만 빼면 두 함수는 완전히 똑같다. 코드 비대화를 일으키는 일반적 형태 중 하나이다. 일반 함수일 경우는 어떻게 하나 ? 크기를 매개변수로 받는 함수를 하나 만들어서 나머지 2개의 함수에서 그것을 호출하는 식으로 만들면 될 것이다. 템플릿도 같은 방법으로 접근이 가능하다.

template<typename T>
class SMBase {
protected:
  void invert(std::size_t n);
};

template<typename T, std::size_t n>
class SquareMatric : private SMBase {
public:
  using SMBase<T>::invert;
  void invert(){ invert(n); } // 암시적 inline 선언
};

 모든 double형의 템플릿들은 크기가 달라도 같은 invert() 함수를 공유하게 된다. 호출에 드는 비용도 추가되지 않았다. invert() 함수가 inline 함수이기 때문이다. 하지만 아직 해결되진 않았다. SMBase의 invert()함수가 어떻게 데이터가 저장된 메모리에 접근을 할 수가 있을까 ? 가장 간단한 방법은 invert() 함수에 메모리 주소를 매개변수로 넣어주는 것인데, 만약 함수가 invert() 한개가 아니라 한 30개 된다면 다 넣을 것인가 ? 이건 좀 아니다. 다른 방법으로 메모리의 포인터를 SMBase가 저장하는 방법이 있다. 어차피 저장하는 김에 크기도 같이 저장하자. 그럼 원래 있던 invert()이 매개변수도 삭제가 가능하다.

template<typename T>
class SMBase {
protected:
  SMBase(std::size_t n, T* pm) : size(n), pData(pm) {}
  void invert();
private:
  std::size_t size;
  T* pData;
};

template<typename T, std::size_t n>
class SquareMatric : private SMBase {
public:
  using SMBase<T>::invert;
  SquareMatrix() : SMBase<T>(n, data) {}
  void invert(){ invert(); }
private:
  T data[n*n];
};

이렇게 만들면 동적 메모리 할당은 안해도 되지만 파생 클래스의 크기가 커진다. 이 방법이 마음에 안들면 데이터를 힙으로 옮기는 방법도 있다.

template<typename T, std::size_t n>
class SquareMatric : private SMBase {
public:
  SquareMatrix() : SMBase<T>(n, NULL), pData(new T[n*n]) {
     this->setDataPtr(pData.get());
  }
  void invert(){ this->invert(); }
private:
  boost::scoped_array<T> pData;
};

 대부분의 파생 클래스의 함수들은 inline 함수가 되어 기본 클래스 버전의 사본을 하나만 공유하면서도 호출 비용의 증가없이 사용이 가능하다.

 또 다른 방법으로는 파생 클래스의 포인터를 Base 템플릿이 가지고 있는 방법도 있다.

 이렇게 했을 경우 단순히 실행 코드 크기가 작아지는 것으로 끝나는 것이 아니다. 프로그램의 작업 세트 크기가 줄어들어 명령어 캐시 내의 참조 지역성도 향상된다. 얼핏보면 포인터 크기 하나만큼의 낭비가 일어나긴 하겠지만, 저걸 없에기 위해서 이런 저런 방법을 찾다간 객체지향적인 특징의 많은 것들을 포기해야 할 수도 있다.

 몇몇 C++ Compiler는 자체적으로 최적화 작업을 해주기도 한다. vector<int> 와 vector<long> 을 합쳐준다던지, list<int *>, ist<const int *>, list<SquareMatrix<long, 3>*> 등을 모두 list<void *>로 동작하게 한다든지 등등...

 * 템플릿을 사용하면 비슷비슷한 클래스와 함수가 여러 벌 만들어집니다. 따라서 템플릿 매개변수에 종속되지 않은 템플릿 코드는 비대화이 원인이 됩니다.

 * 비타입 템플릿 매개변수로 생기는 코드 비대화의 경우, 템플릿 매개변수를 함수 매개변수 혹은 클래스 데이터 맴버로 대체함으로써 비해화를 종종 없앨 수 있습니다.




 * 타입 매개변수로 생기는 코드 비대화의 경우, 동일한 이진 표현구조를 가지고 인스턴스화되는 타입들이 한 가지 함수 구현을 공유하게 만듦으로써 비대화를 감소시킬 수 있습니다.

template으로 만들어진 기본 클래스 안의 이름에 접근하는 방법을 알아 두자.

몇 개 회사에 메세지를 전송하는 응용프로그램을 만들어야 할 경우를 생각해보자. 아래 예제와 같이 구현하였다.

class CompanyA {
public:
 void sendClear(const std::string msg);
 void sendEncrypted(const std::string msg);
};

class CompanyB {
public:
 void sendClear(const std::string msg);
 void sendEncrypted(const std::string msg);
};

class MsgInfo { ... };

template<typename C>
class MsgSender {
 void sendClear(const MsgInfo& info) {
  std::string msg = info.toString();
  C c;
  c.senderClear(msg);
 }
 void sendEncrypted(const MsgInfo& info) { ... };
};

template<typename C>
class LoggingMsgSender : public MsgSender<C> {
 void sendClear(const MsgInfo& info) {
  // 메세지 보내기전 Log 기록
  sendClear(info);             // Compile 시 오류 발생
  // 메세지 보낸 후 Log 기록
 }
};

 위의 Code는 컴파일 되지 않는다. LoggingMsgSender에서 오류를 발생시킨다. template의 경우 Compile-time에 그 형태가 정해지는데, LoggingMsgSender<C> 입장에서 MsgSender<C> 클래스가 어떤 형태인지 알 방법이 없다. sendClear() 함수가 들어 있는지 없는지도 모르는 것이 당연하다. 아래 예제를 한번 보자.

class CompanyZ {
public:
 void sendEncrypted(const std::string msg);
};

template<>                   // CompanyZ 에 대해서만 특수화된 템플릿
class MsgSender<CompanyZ> {
 void sendEncrypted(const MsgInfo& info) { ... };
};

 template<> 라는 낯선 표현이 보인다. 이건 템플릿도 아니고 클래스도 아니다. 특정 Type에 대해서만 사용 가능한 템플릿을 완전 템플릿 특수화(total template specialization) 이라고 한다.

 위의 MsgSenger<CompanyZ> 를 LoggingMsgSender로 보내면 어떻게 될까 ? 저긴 sendClear() 함수 자체가 없다. 이럴 가능성이 있기 때문에 C++ 컴파일러는 템플릿으로 만들어진 기본 클래스를 뒤져서 상속된 이름을 찾는 것을 거부한다.

 이 난국을 돌파하기 위해서는 어떻게든 C++이 "난 템플릿화된 기본 클래스를 멋대로 안 뒤질 거야" 라고 생각하지 않도록 해줘야 한다. 방법에는 세가지가 있다

 첫째, 기본 클래스 함수에 대한 호출문 앞에 "this->"를 붙인다.

template<typename C>
class LoggingMsgSender : public MsgSender<C> {
 void sendClear(const MsgInfo& info) {
  // 메세지 보내기전 Log 기록
  this->sendClear(info);
  // 메세지 보낸 후 Log 기록
 }
};

 둘째, using 선언을 사용한다.

template<typename C>
class LoggingMsgSender : public MsgSender<C> {
 using MsgSender<C>::sendClear;
 void sendClear(const MsgInfo& info) {
  // 메세지 보내기전 Log 기록
  sendClear(info);
  // 메세지 보낸 후 Log 기록
 }
};

 셋째, 명시적으로 지정한다. 하지만 이 방법은 추천하고 싶지 않다. 만약 가상함수인 경우 명시적으로 지정하면 가상 함수 바인딩이 무시된다.
template<typename C>
class LoggingMsgSender : public MsgSender<C> {
 void sendClear(const MsgInfo& info) {
  // 메세지 보내기전 Log 기록
  MsgSender<C>::sendClear(info);
  // 메세지 보낸 후 Log 기록
 }
};

 위 세가지 방법의 동작 원리는 모두 같다. 기본 클래스 템플릿이 이후에 어떻게 특수화되더라도 원래이 일반형 템플릿에서 제공하는 인터페이스를 그대로 제공할 것이라고 C++에게 약속하는 것이다. 그 약속이 비리정치인 선거공약 같은 것이었다는 것이 들통 나면 이후의 컴파일 과정에서 겨레의 응징이 들어갈 것이다.

 * 파생 클래스 템플릿에서 기본 클래스 템플릿의 이름을 참조할 때는 "this->"를 접두사로 붙이거나 기본 클래스 한정문을 명시적으로 써 주는 것으로 해결합시다.

typename의 두 가지 의미를 제대로 파악하자.

template<class T> class Widget;
template<typename T> class Widget;

위의 선언문에서 class와 typename의 차이가 뭘까 ? 없다. 같은 뜻이다. 하지만 typename을 쓰지 않으면 안 되는 때가 분명히 있다.

 템플릿 안에서 참조할 수 있는 이름에는 두 가지 종류가 있다. 하나는 템플릿 매개변수에 종속되는 중첩 의존 타입 이름(nested dependent type name) 이고 다른하나는 int와 같이 템플릿 매개변수와는 상관없는 비의존 이름(non-dependent name) 이다.

template<typename C>
void func(const C& container)
{
  C::const_iterator * x;
}




 위 Code에서 C::const_iterator가 타입이 아니라 C의 멤버 데이터라면 어쩔것인가 ? 그리고 x가 전역 변수라면 저건 두 변수의 곱인가 ? 말도 안되는 상상이라고 생각할 수 있지만 충분히 가능한 일이다. C++는 이런 모호성을 해결하기 위해 규칙을 하나 정했다. 템플릿 안에 충첩 의존 이름을 만나면 프로그래머가 타입이라고 알려주지 않는 이상 무조건 타입이 아닌 것으로 취급한다. 그러니 이 난국을 바로 잡을 방법은 하나 밖에 없다. 앞에다가 typename 키워드를 써주는 것이다.

template<typename C>
void func(const C& container)
{
  typename C::const_iterator * x;
}

 중첩 의존 타입 이름 앞에 typename을 붙이면 안되는 예외가 하나 있다. 기본 클래스의 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우에는 typename을 붙여주면 안된다. 무슨 말인지 모르겠다고 ? 아래 Code를 보자.

template<typename T>
class Derived : public Base<T>::Nested { // 상속되는 기본 클래스 리스트
public:
  explicit Derived(int x)
    : Base<T>::Nested(x) // 멤버 초기화 리스트에 있는 기본 클래스 식별자
  {
    typename Base<T>::Nested temp; // typename 꼭 붙여야 함
  }
};

 현업에서 자주 사용하는 Code 중 반복자를 매개변수로 받는 어떤 함수 템플릿이 있는데, 매개변수로 넘어온 반복자가 가리키는 객체의 사본을 만드는 경우가 있다. 객체로 부터 그 Type을 추출해 내는 과정으로 C# 과 Java의 Reflect를 사용하는 것과 비슷한 경우이다. 이럴 경우 typename이 여러번 쓰인다면 무지막지하게 길게 되어서 typedef를 사용하는 것이 효과적인 경우가 많다. 보통 그럴경우 그 멤버 이름과 똑같은 이름으로 짓는 것이 관례로 되어 있다.

template<typename T>
void workWithIterator<T iter> {
  typedef typename std::iterator_traits<T>::value_type value_type;
  value_type temp(*iter);
}

 * 템플릿 매개변수를 선언할 때, class 및 typename은 서로 바꾸어 써도 무방합니다.

 * 중첩 의존 타입 이름을 식별하는 용도에는 반드시 typename을 사용합니다. 단, 중첩 의존 이름이 기본 클래스 리스트에 있거나 멤버 초기화 리스트 내의 기본 클래스 식별자로 있는 경우에는 예외입니다.

Template 프로그래밍의 천리길도 암시적 인터페이스와 컴파일 타임 다형성부터

 객체 지향 프로그래밍 (OOP)의 세계를 회전시키는 축은 명시적 인터페이스(explicit interface)런타임 다형성(runtime polymorphism) 이다.
 명시적 인터페이스(explicit interface) 란 소스코드 (header 파일 등) 에 선언된 Code를 보고 interface를 확인 할 수 있는 것을 가리키는 말이며, 런타임 다형성(runtime polymorphism) 이란 virtual로 선언된 가상 함수의 실제 호출은 동적 타임을 기반으로 프로그램 실행 중, 즉 런타임에 결정된다는 말이다.

 템플릿과 일반화 프로그래밍의 세계는 뿌리부터 뭔가 다른 부분이 있다. 이 바닥에서 홀개를 치고 다니는 주인공은 암시적 인터페이스(inplicit interface)컴파일 타임 다형성(compile-time polymorphism)이다.

template<typename T>
void doProcessing(T& w)
{
  if (w.size() > 10 && w != someNastyWidget) {
    T temp(w);
    temp.normalize();
    temp.swap(w);
  }
}

 위의 Code에서 모르긴 몰라도 T의 객체는 size(), normalize(), swap() 맴버 함수를 가져야 한다. 이 템플릿이 제대로 컴파일되려면 사용된 표현식들이 유효(valid)해야 하는데, 이 표현식들이 바로 T 가 지원해야 하는  암시적 인터페이스(inplicit interface)이다.
 operator > 및 operator != 함수가 호출될 때 템플릿의 인스턴스화가 일어난다. 이것은 컴파일 도중에 일어난다. 인스턴스화를 진행하는 함수 템플릿에 어떤 템플릿 매개변수가 들어가느냐에 따라 호출되는 함수가 달라지기 때문에 이것을 컴파일 타임 다형성(compile-time polymorphism)이라 한다.

 명시적 인터페이스(explicit interface)는 함수 시그너처로 이루어지지만, 암시적 인터페이스(inplicit interface)는 표현식(expression)으로 이루어진다. 위의 Code를 봤을때 T의 객체는 정수 계열의 값을 return 하는 size() 함수를 지원해야 하며 T 타입 객체 둘을 비교하는 operator != 함수를 지원해야 한다고 보여지지만 꼭 그런것은 아니다. 실제로는 연산자 오버로딩의 가능성도 있고, size()는 꼭 수치값을 return 할 필요도 없다. if 안의 문장전체의 처리 결과가 boolean 형식의 값으로 표현만 되면 유효하다.

 * 클래스 및 템플릿은 모두 인터페이스와 다형성을 지원합니다.

 * 클래스의 경우, 인터에피스는 명시적이며 함수의 시그너처를 중심으로 구성되어 있습니다. 다형성은 프로그램 실행 중에 가상 함수를 통해 나타납니다.

 * 템플릿 매개변수의 경우, 인터페이스는 암시적이며 유효 표현식에 기반을 두어 구성됩니다. 다형성은 컴파일 중에 템플릿 인스턴스화와 함수 오버로딩 모호성 해결을 통해 나타납니다.