본문 바로가기
프로그래밍/C++

C++ ] 함수 템플릿, 클래스 템플릿

by eteo 2025. 12. 30.
반응형

 

 

템플릿

C++에서 템플릿은 타입에 의존하지 않는 일반화된 코드 구조를 정의하는 문법이다. 템플릿을 사용하면 함수나 클래스를 작성할 때 특정 타입을 박아두지 않고, 나중에 어떤 타입으로도 재사용할 수 있어 코드 중복을 효과적으로 줄일 수 있다.

 

 

 

 

 

템플릿 선언 방식

템플릿은 함수와 클래스 모두에 적용할 수 있다. 선언 방식은 동일하며, 함수와 클래스 위에 다음과 같이 선언한다.

 

template <typename T>

 

여기서 typename T는 템플릿 내부에서 실제 타입을 대신하는 형식 매개변수이다. 템플릿이 사용될 때 컴파일러가 이 T를 구체적인 타입(int, double, std::string 등)으로 치환한다.

 

 

 

 

 

템플릿의 구현 위치

일반적인 C++ 코드가 선언은 .h 파일에 작성하고 정의는 .cpp 파일에 분리하여 작성하는 것과 달리, 템플릿의 정의은 예외적으로 .h 파일에 작성해야 한다.

이는 템플릿이 컴파일 시점에 실제 사용되는 타입에 맞춰 코드를 생성하는 방식으로 동작하기 때문이다. 컴파일러가 특정 타입에 맞는 코드를 생성하려면 해당 템플릿이 사용되는 모든 지점에서 전체 정의를 참조할 수 있어야 하므로, 함수 템플릿과 클래스 템플릿은 헤더파일에 구현한다.

 

 

 

 

 

 

함수 템플릿

템플릿 함수는 보통 특정 타입에 구애받지 않고 동작하는 범용적인 알고리즘(max, min, swap, sort 등)을 구현할 때 사용한다.

 

아래는 타입에 상관없이 + 연산을 수행하는 템플릿 함수의 예시이다.

 

#include <iostream>

template <typename T>
T add(T a, T b) {
    return a + b;
}

int main() {
    std::cout << add<int>(1, 2) << std::endl;
    std::cout << add(2.1, 1.04) << std::endl;
    std::cout << add<std::string>("a","b") << std::endl;
    return 0;
}

 

3
3.14
ab

 

템플릿 함수 호출 시 add<int>처럼 타입을 직접 명시할 수 있고, 그렇게하지 않아도 컴파일러가 타입을 추론해준다.

 

 

 

 

 

클래스 템플릿

클래스 템플릿은 보통 클래스가 저장하거나 관리하는 데이터 타입은 서로 다르지만, 데이터의 처리 방식이 동일한 경우 이를 일반화하여 정의할 때 사용한다.

 

아래는 클래스 템플릿을 이용해 Stack 자료구조를 간단히 구현한 예시이다. 실제 C++ STL(Standard Template Library)의 주요 컨테이너들은 모두 클래스 템플릿으로 구현되어 있다. (물론 std::stack은 아래 예시보다 훨씬 더 정교하게 설계되어있다.)

또한, std::unique_ptr<T>, std::shared_ptr<T> 같은 스마트 포인터도 관리 대상 객체의 타입을 일반화하기 위해 클래스 템플릿을 사용한다.

 

#include <iostream>
#include <vector>

template <typename T>
class Stack {
public:
    void push(const T& v) { items.push_back(v); }
    void pop() { items.pop_back(); }
    const T& top() const { return items.back(); }
    bool empty() const { return items.empty(); }
private:
    std::vector<T> items;
};

int main() {
    
    Stack<int> intStack;
    intStack.push(10);
    intStack.push(20);
    intStack.pop();
    std::cout << intStack.top() << std::endl;

    Stack<std::string> stringStack;
    stringStack.push("Hello");
    stringStack.push("abcde");

    std::cout << stringStack.top() << std::endl;
    
    return 0;
}

 

10
abcde

 

 

 

 

 

 

 

 

 

 

템플릿 특수화

템플릿은 기본적으로 모든 타입에 대해 동일한 구현을 제공한다. 그러나 특정 타입이나 타입 패턴에 대해 다른 동작이 필요한 경우, 템플릿 특수화(template specialization)를 사용한다.

템플릿 특수화에는 전체 특수화와 부분 특수화가 있으며, 기본 템플릿이 정의된 상태에서 특정 타입(또는 타입 조건)에 대해 예외적인 구현을 제공한다. 사용 시 타입이 매칭되는 특수화된 템플릿이 존재할 경우, 해당 구현이 기본 템플릿보다 우선 적용된다.

 

 

 

전체 특수화

전체 특수화는 특정 타입에 대해서만 템플릿의 구현을 새로 정의하는 방식이다.

template <>를 사용해 템플릿 파라미터가 없음을 명시하고, 클래스명 또는 함수명 뒤의 템플릿 인자(<...>)에 특수화할 구체적인 타입을 지정한다.

 

전체 특수화 예시.

 

#include <iostream>
#include <cstring>

// 기본 템플릿 함수
template <typename T>
bool isEqual(T a, T b) {
    return a == b;
}

// 전체 특수화
template <>
bool isEqual<const char*>(const char* a, const char* b) {
    std::cout << "const char* specialization called" << std::endl;
    return std::strcmp(a, b) == 0;
}

// 전체 특수화
template <>
bool isEqual<char*>(char* a, char* b) {
    std::cout << "char* specialization called" << std::endl;
    return std::strcmp(a, b) == 0;
}

int main() {

    const char* s1 = "abc";
    const char* s2 = "abc";

    char a[] = "abc";
    char b[] = "abc";
    std::cout << isEqual(10, 10) << std::endl;
    std::cout << isEqual(3.14, 3.14) << std::endl;
    std::cout << isEqual("abc", "abc") << std::endl;
    std::cout << isEqual(s1, s2) << std::endl;
    std::cout << isEqual(a, b) << std::endl;

    return 0;
}

 

1
1
const char* specialization called
1
const char* specialization called
1
char* specialization called
1

 

char*나 const char*는 기본적으로 == 연산을 사용하면 문자열 내용이 아니라 주소값을 비교한다. 이처럼 기본 템플릿이 특정 타입에서는 의도와 다른 동작을 할 때, 템플릿 특수화를 사용해 예외적인 구현을 제공할 수 있다.

 

 

 

 

 

부분 특수화

부분 특수화는 특정 타입 하나가 아니라, 포인터 타입이나 const 타입 같이 특정 타입 패턴에 대해 템플릿을 특수화하는 방식이다.

 

부분 특수화에서는 template <typename T>의 템플릿 파라미터는 그대로 유지한 채, 클래스명 뒤의 템플릿 인자(<...>)에는 T*, const T와 같은 타입 패턴을 지정하여 선언한다.

 

한편, 부분 특수화는 클래스 템플릿에서만 사용할 수 있다. 함수 템플릿에서는 부분 특수화를 지원하지 않지만, 함수 오버로딩을 통해 특정 타입 패턴에 대한 예외 처리를 구현할 수 있다.

 

부분 특수화 예시.

 

#include <iostream>

// C++에서 struct와 class는 기본 '접근 지정자'와 '상속 접근자'만 다를 뿐 둘 다 class 타입이다.
// 기본 템플릿 클래스
template <typename T>
struct TypeInfo {
    TypeInfo() {
        std::cout << "Generic type" << std::endl;
    }
};

// 부분 특수화
template <typename T>
struct TypeInfo<T*> {
    TypeInfo() {
        std::cout << "Pointer type" << std::endl;
    }
};

// 부분 특수화
template <typename T>
struct TypeInfo<const T> {
    TypeInfo() {
        std::cout << "Const type" << std::endl;
    }
};

// 부분 특수화
template <typename T>
struct TypeInfo<const T*> {
    TypeInfo() {
        std::cout << "Pointer to const type" << std::endl;
    }
};

int main() {
    TypeInfo<int> a;
    TypeInfo<int*> b;
    TypeInfo<const int> c;
    TypeInfo<const int*> d;

    return 0;
}

 

Generic type
Pointer type
Const type
Pointer to const type

 

 

함수 템플릿의 오버로딩 예시.

#include <iostream>

// 함수 템플릿
template <typename T>
void func(T value) {
    std::cout << "non-pointer\n";
}

// 함수 템플릿
// 부분 특수화가 아니라 함수 오버로딩이고, 서로 별개의 함수 템플릿 두 개다.
template <typename T>
void func(T* value) {
    std::cout << "pointer\n";
}

int main()
{
    int a = 10;
    int* p = &a;
    
    func(a);   // non-pointer type
    func(p);   // pointer type

    return 0;
}

 

non-pointer
pointer

 

 

 

 

가변인자 템플릿

2024.08.18 - [프로그래밍/C++] - C++ ] 가변 인자 템플릿(Variadic Templates) 활용

반응형

'프로그래밍 > C++' 카테고리의 다른 글

C++ ] 싱글톤(Singleton) 클래스  (0) 2025.12.15
C++ ] constexpr  (1) 2025.11.30
C++] fstream의 open mode  (0) 2025.07.24
C++] Intel hex to bin 변환 프로그램 (TI C2000 시리즈)  (0) 2025.05.24
C++] std::chrono 라이브러리  (0) 2025.05.12