[C++] 항목 48: 템플릿 메타프로그래밍, 하지 않겠는가?
카테고리: Cpp
태그: Cpp
이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역
📦 7. 템플릿과 일반화 프로그래밍
👉🏻 항목 48: 템플릿 메타프로그래밍, 하지 않겠는가?
✅ 템플릿 메타프로그래밍(TMP)이란?
템플릿 메타프로그래밍(TMP): 컴파일 도중 실행되는 템플릿 기반의 프로그램을 작성하는 일
✨ TMP의 장점
1. 불가능을 가능으로
- TMP를 사용하면, 까다롭거나 불가능한 일을 쉽게 처리할 수 있다
2. 실행 시점 이동
- 컴파일 타임에 실행되기에, 기존 작업을 런타임에서 컴파일 타임으로 전환할 수 있다
- 컴파일 도중에 에러를 찾을 수 있게 된다
3. 성능 향상
- 컴파일 시간이 길어지는 대신:
- 실행 코드가 작아진다
- 실행 시간도 짧아진다
- 메모리도 적게 사용한다
⚠️ 항목 47의 문제: typeid 사용
template<typename IterT, typename DistT>
void advance(IterT& iter, DistT d) {
if(typeid(typename iterator_traits<IterT>::iterator_category)
== typeid(random_access_iterator_tag)
) {
iter += d;
} else {
if(d >= 0) { while(d--) ++iter; }
else { while(d++) --iter; }
}
}
문제점:
- 항목 47의
advance함수이다 - TMP를 사용하면 타입정보를 꺼내는 작업(typeid)을 런타임에 할 수 있다
typeid는 특성정보(traits)를 쓰는 방법보다 효율이 떨어진다:- 타입 점검 동작이 컴파일이 아닌 런타임에 일어난다
- 런타임 타입 점검을 수행하는 코드는 실행 파일에 들어갈 수밖에 없다
해결:
- 우리는 이미 항목 47에서 TMP를 사용했었다
iterator_traits를 함수 오버로딩에 사용함
🚨 항목 47에서 컴파일 문제가 있었던 이유
list<int>::iterator iter;
...
advance(iter, 10);
만약, 위와 같이 advance 함수를 호출한다 가정하자.
void advance(list<int>::iterator& iter, int d) {
if(typeid(iterator_traits<list<int>::iterator>::iterator_category)
== typeid(random_access_iterator_tag)
) {
iter += d; // ❌ 컴파일 에러!
} else {
if(d >= 0) { while(d--) ++iter; }
else { while(d++) --iter; }
}
}
문제:
advance함수는 해당 코드와 같이 인스턴스화된다iter += d부분에서 컴파일 에러가 난다list는 양방향 반복자이기 때문에+=연산을 지원하지 않음- 런타임에 if문으로 분기해도, 컴파일 시점에 모든 분기의 코드가 생성되므로 에러 발생
📌 TMP의 특성
튜링 완전성:
- TMP는 튜링 완전성을 갖는다
- 튜링 완전성: 범용 프로그래밍 언어와 같이 어떤 것이든 계산할 수 있는 능력이 있는 것
기본 기능:
- 변수 선언, 루프 실행, 함수 작성/호출 가능하다
- 일반적인 if문이 아닌 템플릿 및 템플릿 특수화 버전을 사용한다
- 반복(iteration) 의미의 루프는 없고, 재귀를 사용하여 루프의 효과를 낼 수 있다
- TMP의 루프는 재귀식 템플릿 인스턴스화를 한다
✅ TMP 예제: 팩토리얼 계산
template<unsigned n>
struct Factorial {
enum { value = n * Factorial<n-1>::value };
};
// 템플릿 특수화
// Factorial<0> = 1이다.
template<>
struct Factorial<0> {
// 나열자 둔갑술(항목 2)
enum { value = 1 };
};
int main() {
// 컴파일 시간에 계산 가능
cout << Factorial<5>::value; // 120
cout << Factorial<10>::value; // 3628800
}
동작 원리:
Factorial템플릿은 구조체 타입이 인스턴스화되도록 만들어져 있다- 재귀식 템플릿 인스턴스화를 사용하여,
템플릿 인스턴스화 버전마다 자체적으로 value의 사본을 갖게 되는 방식이다 - 나열자 둔갑술이 쓰이고 있다
- 런타임이 아닌, 컴파일 시간에 계산된다
✨ TMP의 실용적 활용
1. 치수 단위(Dimensional Unit)의 정확성 확인
문제 상황:
- 속도 변수에 질량을 대입하는 실수
해결:
- TMP를 사용하면, 컴파일 타임에 에러를 확인할 수 있다
- 이를 선행 에러 탐지(Early Error Detection)라고 한다
- 분수식 지수 표현도 지원된다
예시:
// 개념적 예시
Velocity v = 100; // m/s
Mass m = 50; // kg
// ❌ 컴파일 타임 에러: 타입 불일치
v = m;
// ✅ 올바른 사용
Force f = m * Acceleration(2.0); // F = ma
2. 행렬 연산의 최적화
문제 코드:
typedef SquareMatrix<double, 10000> BigMatrix;
// 행렬 생성
BigMatrix m1, m2, m3, m4, m5;
... // 각 행렬에 값 대입
// 행렬 곱 계산
BigMatrix result = m1 * m2 * m3 * m4 * m5;
문제점:
- operator* 연산을 한번 수행할 때마다, 임시 행렬이 생성된다
- 곱연산을 4번하니, 4개의 임시 행렬이 생성된다
- 이거 완전 발적화다
해결: 표현식 템플릿(Expression Template)
- TMP를 응용한 표현식 템플릿을 사용하면:
- 임시 객체가 필요없다
- 루프도 합칠 수 있다
- 성능이 극적으로 향상된다
3. 맞춤식 디자인 패턴 구현의 생성
문제:
- 디자인 패턴은 구현 방법이 여러 가지일 수 있다
해결: 정책 기반 설계(Policy-Based Design)
- TMP를 응용한 정책 기반 설계를 사용하면:
- 따로따로 마련된 설계상의 선택(정책)을 나타내는 템플릿을 만들 수 있다
- 정책 템플릿은 서로 조합하여 원하는 동작의 패턴을 구현할 수 있다
- 이것이 블록을 조립하여, 원하는 것을 만들어내는
생성식 프로그래밍(Generative Programming)의 기초이다
예시 개념:
// 다양한 정책들
template<typename T> class ThreadingPolicy { ... };
template<typename T> class LockingPolicy { ... };
template<typename T> class StoragePolicy { ... };
// 정책들을 조합한 클래스
template<
typename T,
template<typename> class Threading = NoThreading,
template<typename> class Locking = NoLocking,
template<typename> class Storage = DefaultStorage
>
class SmartPtr : public Threading<T>,
public Locking<T>,
public Storage<T> {
// 조합된 정책에 따라 동작
};
📊 런타임 vs 컴파일 타임 비교
| 특성 | 런타임 | 컴파일 타임 (TMP) |
|---|---|---|
| 에러 검출 | 실행 중 | 컴파일 중 |
| 실행 속도 | 느림 (연산 수행) | 빠름 (이미 계산됨) |
| 코드 크기 | 작음 | 증가 가능 |
| 컴파일 시간 | 빠름 | 느림 |
| 유연성 | 높음 (동적) | 낮음 (정적) |
💡 TMP 사용 시 고려사항
장점:
- ✅ 선행 에러 탐지
- ✅ 높은 런타임 효율
- ✅ 타입 안전성
- ✅ 최적화된 코드 생성
단점:
- ⚠️ 긴 컴파일 시간
- ⚠️ 복잡한 문법
- ⚠️ 디버깅 어려움
- ⚠️ 코드 크기 증가 가능
🧐 정리
- 템플릿 메타프로그래밍은 기존 작업을 런타임에서 컴파일 타임으로 전환하는 효과를 낸다.
따라서, TMP를 사용하면 선행 에러 탐지, 높은 런타임 효율을 얻을 수 있다. - TMP는 아래와 같이 사용할 수 있다:
- 정책 선택의 조합에 기반하여 사용자 정의 코드 생성
- 특정 타입에 대해 부적절한 코드가 만들어지는 것을 방지
- TMP는 튜링 완전성을 가지며, 재귀를 통해 반복을 구현한다.
템플릿 특수화를 통해 분기 처리가 가능하다. - TMP의 주요 활용 사례:
- 치수 단위 정확성 검증 (컴파일 타임 타입 체크)
- 표현식 템플릿을 통한 성능 최적화
- 정책 기반 설계를 통한 유연한 컴포넌트 조합
댓글남기기