1. 비타입 템플릿 매개변수
템플릿 매개변수는 크게 두 가지로 구분됩니다. class나 typename 키워드 뒤에 오는 타입 매개변수와, 컴파일 타임 상수를 받는 비타입 매개변수입니다.
비타입 매개변수는 템플릿 내부에서 상수처럼 활용됩니다. 고정 크기 컨테이너를 구현할 때 유용하게 쓰입니다.
namespace util {
template<typename ElemType, std::size_t Capacity = 16>
class FixedBuffer {
public:
ElemType& at(std::size_t idx) { return _data[idx]; }
const ElemType& at(std::size_t idx) const { return _data[idx]; }
std::size_t length() const noexcept { return _len; }
private:
ElemType _data[Capacity];
std::size_t _len = 0;
};
}
제약사항:
- 정수 계열 타입(integral types)만 허용됩니다
- 컴파일 시점에 반드시 결정되어야 합니다
2. 템플릿 특수화
범용 템플릿으로는 적절히 처리되지 않는 특수한 타입에 대해 별도의 구현을 제공하는 메커니즘입니다.
2.1 함수 템플릿 특수화
포인터 비교가 필요한 경우를 예로 들어봅시다. 일반적인 비교 연산자는 주소값을 비교하므로, 실제 내용 비교가 필요합니다.
template<typename T>
bool is_equal(T const& lhs, T const& rhs) {
return lhs == rhs;
}
// const char*에 대한 특수화
template<>
bool is_equal<const char*>(const char* const& lhs, const char* const& rhs) {
return std::strcmp(lhs, rhs) == 0;
}
실무에서는 함수 오버로딩이 특수화보다 선호됩니다. 타입 추론이 자연스럽고, 코드가 간결해집니다.
2.2 클래스 템플릿 특수화
완전 특수화
모든 템플릿 매개변수를 구체적인 타입으로 고정합니다.
template<typename K, typename V>
class Pair {
public:
Pair() { std::cout << "일반 버전\n"; }
private:
K _key;
V _value;
};
template<>
class Pair<int, std::string> {
public:
Pair() { std::cout << "int-string 특수화\n"; }
private:
int _id;
std::string _name;
};
부분 특수화
일부 매개변수만 특수화하거나, 타입에 대한 추가 제약을 둡니다.
// 두 번째 매개변수만 특수화
template<typename K>
class Pair<K, double> {
public:
Pair() { std::cout << "double value 특수화\n"; }
};
// 포인터 타입에 대한 특수화
template<typename K, typename V>
class Pair<K*, V*> {
public:
Pair() { std::cout << "이중 포인터 특수화\n"; }
Pair(K* k, V* v) : _ptr_key(k), _ptr_val(v) {}
private:
K* _ptr_key;
V* _ptr_val;
};
// 참조 타입에 대한 특수화
template<typename K, typename V>
class Pair<K&, V&> {
public:
Pair(K& k, V& v) : _ref_key(k), _ref_val(v) {
std::cout << "참조 특수화\n";
}
private:
K& _ref_key;
V& _ref_val;
};
3. 분리 컴파일 문제
템플릿의 선언과 정의를 헤더와 소스 파일로 분리하면 링크 에러가 발생합니다. 이는 템플릿이 인스턴스화 시점에 실제 코드를 생성하기 때문입니다.
해결 방안:
- 헤더 파일에 정의를 포함 (권장)
- 명시적 인스턴스화 선언 (유지보수 부담)
// 해결책 1: 헤더 기반 구현
// math_utils.hpp
#pragma once
template<typename T>
T square(T val) { return val * val; }
// 해결책 2: 명시적 인스턴스화
// math_utils.cpp
template int square<int>(int);
template double square<double>(double);
4. 설계 고려사항
| 측면 | 장점 | 단점 |
|---|---|---|
| 코드 재사용 | 알고리즘과 자료구조의 일반화 | STL과 같은 표준 라이브러리 기반 |
| 유연성 | 컴파일 타임 다형성 | 빌드 시간 증가 |
| 디버깅 | 타입 안전성 | 에러 메시지의 복잡성 |
| 바이너리 | 인라인 최적화 가능 | 코드 중복으로 인한 크기 증가 |