왜 템플릿인가? 반복 코드에서 벗어나는 첫걸음
정수, 실수, 문자 등 다양한 타입에 대해 동일한 로직을 구현할 때마다 함수를 반복해서 작성해본 경험이 있다면, C++의 템플릿은 그 해결책이 될 수 있다. 예를 들어 두 값을 교환하는 Swap 함수를 각 타입별로 따로 작성하면 다음과 같다:
void Swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void Swap(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
이 방식은 논리적 중복이 크며 유지보수와 확장성이 떨어진다. 이 문제를 해결하기 위해 C++은 템플릿을 통해 제네릭 프로그래밍을 지원한다.
함수 템플릿: 하나의 설계도로 모든 타입 처리
함수 템플릿은 특정 타입에 종속되지 않는 일반화된 함수 형태를 제공한다. 기본 문법은 다음과 같다:
template<typename T>
void Swap(T& left, T& right) {
T temp = left;
left = right;
right = temp;
}
이제 아래처럼 다양한 타입에 대해 동일한 함수를 사용할 수 있다:
int x = 10, y = 20;
Swap(x, y); // 컴파일러가 자동으로 T=int 추론
double a = 3.14, b = 2.71;
Swap(a, b); // T=double
템플릿의 작동 원리: 컴파일 타임 코드 생성
템플릿 자체는 실행 가능한 함수가 아니다. 대신 컴파일러에게 "이런 형태의 함수를 만들어라"는 지침을 제공한다. 실제 함수는 호출 시점에 인자 타입에 따라 인스턴스화된다. 이를 템플릿 인스턴스화라고 한다.
인스턴스화 방식: 암시적 vs 명시적
암시적 인스턴스화는 인자의 타입을 보고 컴파일러가 자동으로 결정한다:
template<class T>
T Add(const T& a, const T& b) {
return a + b;
}
Add(5, 7); // T = int
Add(3.5, 2.1); // T = double
하지만 Add(5, 3.14)처럼 혼합 타입을 전달하면 오류가 발생한다. 템플릿은 각 매개변수의 타입을 독립적으로 추론하므로 일치하지 않기 때문이다.
이 경우 명시적 인스턴스화를 사용하여 타입을 강제할 수 있다:
Add<double>(5, 3.14); // T를 double로 고정, int → double 변환
함수 우선순위 규칙: 템플릿 vs 일반 함수
같은 이름의 일반 함수와 템플릿 함수가 동시에 존재할 경우 다음 규칙이 적용된다:
- 일반 함수가 우선 호출된다.
- 템플릿을 강제로 사용하려면
<>로 명시해야 한다. - 템플릿이 더 적절한 타입 조합을 제공할 수 있으면 해당 버전이 선택될 수 있다.
// 일반 함수
int Add(int a, int b) { return a + b; }
// 템플릿 함수
template<class T>
T Add(T a, T b) { return a + b; }
Add(1, 2); // 일반 함수 호출
Add<float>(1, 2); // 템플릿 강제 호출
클래스 템플릿: 재사용 가능한 컨테이너 설계
함수뿐만 아니라 클래스도 템플릿화할 수 있다. 대표적인 예는 스택과 같은 컨테이너이다:
template<typename T>
class Stack {
private:
T* buffer;
size_t topIndex;
size_t capacity;
public:
explicit Stack(size_t cap = 8) : capacity(cap), topIndex(0) {
buffer = new T[capacity];
}
void push(const T& value);
void pop();
const T& top() const;
~Stack() { delete[] buffer; }
};
// 멤버 함수 외부 정의 시 템플릿 선언 필요
template<typename T>
void Stack<T>::push(const T& value) {
if (topIndex < capacity) {
buffer[topIndex++] = value;
}
}
클래스 템플릿 인스턴스화
템플릿 클래스는 사용할 때 타입을 명시해야 비로소 완전한 타입이 된다:
Stack<int> intStack;
Stack<std::string> stringStack;
주의 사항 및 실수 방지 팁
- 헤더 파일(.h)에 템플릿 선언과 정의를 모두 포함해야 한다. 분리하면 링크 오류 발생.
template<typename T>이후 세미콜론 없이 바로 정의 시작.- 멤버 함수를 외부에서 구현할 때는
template<typename T>접두사 필수. typename과class는 템플릿 문맥에서 동일하게 사용 가능.
핵심 요약
| 항목 | 설명 |
|---|---|
| 제네릭 프로그래밍 | 타입에 독립적인 코드 설계 |
| 함수 템플릿 | 타입을 매개변수화하여 다형성 확보 |
| 인스턴스화 | 암시적(추론), 명시적(강제) |
| 호출 우선순위 | 일반 함수 > 템플릿 함수 |
| 클래스 템플릿 | Stack<int> 형태로 사용, .h 내에 전체 포함 권장 |
마무리
C++ 표준 라이브러리(STL)의 핵심인 vector, map, queue 등 대부분은 클래스 템플릿으로 구현되어 있다. 템플릿을 이해하는 것은 STL을 깊이 있게 활용하고, 효율적이며 재사용 가능한 코드를 작성하는 첫걸음이다.