icon안동민 개발노트

템플릿 특수화


템플릿 특수화의 개념

 템플릿 특수화는 C++의 강력한 기능 중 하나로, 특정 타입이나 조건에 대해 템플릿의 다른 구현을 제공할 수 있게 해줍니다.

 이를 통해 일반적인 알고리즘을 유지하면서도 특정 경우에 대한 최적화나 특별한 처리를 할 수 있습니다.

 템플릿 특수화의 주요 목적

  1. 특정 타입에 대한 최적화
  2. 특별한 동작이 필요한 타입 처리
  3. 컴파일 타임 최적화

함수 템플릿 특수화

 일반 함수 템플릿

 먼저, 일반적인 함수 템플릿을 살펴봅시다.

template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

 이 템플릿은 대부분의 타입에 대해 잘 작동하지만, 문자열(C-style string)과 같은 특정 타입에 대해서는 적절하지 않을 수 있습니다.

 특수화된 함수 템플릿

 문자열을 위한 특수화된 버전을 만들어 봅시다.

template <>
const char* max<const char*>(const char* a, const char* b) {
    return (strcmp(a, b) > 0) ? a : b;
}

 이제 max 함수는 문자열에 대해 올바르게 동작합니다.

int main() {
    std::cout << max(10, 20) << std::endl;  // 출력: 20
    std::cout << max("apple", "banana") << std::endl;  // 출력: banana
 
    return 0;
}

클래스 템플릿 특수화

 일반 클래스 템플릿

 다음과 같은 일반 클래스 템플릿이 있다고 가정해봅시다.

template <typename T>
class Container {
private:
    T element;
public:
    void set(T arg) { element = arg; }
    T get() const { return element; }
};

 완전 특수화

 bool 타입에 대해 메모리를 절약하는 특수화를 구현할 수 있습니다.

template <>
class Container<bool> {
private:
    unsigned char element : 1;
public:
    void set(bool arg) { element = arg; }
    bool get() const { return element; }
};

 이 특수화는 bool 값을 저장할 때 1비트만 사용합니다.

 부분 특수화

 부분 특수화는 템플릿 매개변수의 일부만 특수화하는 것을 말합니다. 클래스 템플릿에서만 가능합니다.

// 일반 템플릿
template <typename T, typename U>
class MyClass {
public:
    void print() { std::cout << "General template" << std::endl; }
};
 
// T가 int인 경우에 대한 부분 특수화
template <typename U>
class MyClass<int, U> {
public:
    void print() { std::cout << "Partial specialization for int" << std::endl; }
};
 
// 포인터 타입에 대한 부분 특수화
template <typename T, typename U>
class MyClass<T*, U*> {
public:
    void print() { std::cout << "Partial specialization for pointers" << std::endl; }
};
사용 예시
int main() {
    MyClass<double, float> obj1;
    MyClass<int, float> obj2;
    MyClass<int*, float*> obj3;
 
    obj1.print();  // 출력: General template
    obj2.print();  // 출력: Partial specialization for int
    obj3.print();  // 출력: Partial specialization for pointers
 
    return 0;
}

템플릿 특수화의 장단점

 장점

  1. 타입별 최적화 가능
  2. 특정 타입에 대한 특별한 동작 구현 가능
  3. 컴파일 타임 최적화

 단점

  1. 코드 복잡성 증가
  2. 유지보수의 어려움
  3. 과도한 사용 시 코드 중복 가능성

실습 : 스마트 포인터 템플릿

 다음 요구사항을 만족하는 간단한 스마트 포인터 템플릿을 구현해봅시다.

  1. SmartPtr 클래스 템플릿 작성
  2. 일반 타입과 배열 타입에 대한 특수화 구현
  3. 참조 카운팅 기능 추가
#include <iostream>
 
// 일반 템플릿
template <typename T>
class SmartPtr {
private:
    T* ptr;
    int* refCount;
 
public:
    SmartPtr(T* p = nullptr) : ptr(p), refCount(new int(1)) {
        std::cout << "Creating SmartPtr (general)" << std::endl;
    }
 
    SmartPtr(const SmartPtr& other) : ptr(other.ptr), refCount(other.refCount) {
        (*refCount)++;
        std::cout << "Copying SmartPtr (general)" << std::endl;
    }
 
    ~SmartPtr() {
        std::cout << "Destroying SmartPtr (general)" << std::endl;
        if (--(*refCount) == 0) {
            delete ptr;
            delete refCount;
        }
    }
 
    T& operator*() { return *ptr; }
    T* operator->() { return ptr; }
 
    SmartPtr& operator=(const SmartPtr& other) {
        if (this != &other) {
            if (--(*refCount) == 0) {
                delete ptr;
                delete refCount;
            }
            ptr = other.ptr;
            refCount = other.refCount;
            (*refCount)++;
        }
        return *this;
    }
};
 
// 배열에 대한 부분 특수화
template <typename T>
class SmartPtr<T[]> {
private:
    T* ptr;
    int* refCount;
 
public:
    SmartPtr(T* p = nullptr) : ptr(p), refCount(new int(1)) {
        std::cout << "Creating SmartPtr (array)" << std::endl;
    }
 
    SmartPtr(const SmartPtr& other) : ptr(other.ptr), refCount(other.refCount) {
        (*refCount)++;
        std::cout << "Copying SmartPtr (array)" << std::endl;
    }
 
    ~SmartPtr() {
        std::cout << "Destroying SmartPtr (array)" << std::endl;
        if (--(*refCount) == 0) {
            delete[] ptr;
            delete refCount;
        }
    }
 
    T& operator[](int index) { return ptr[index]; }
 
    SmartPtr& operator=(const SmartPtr& other) {
        if (this != &other) {
            if (--(*refCount) == 0) {
                delete[] ptr;
                delete refCount;
            }
            ptr = other.ptr;
            refCount = other.refCount;
            (*refCount)++;
        }
        return *this;
    }
};
 
int main() {
    SmartPtr<int> sp1(new int(5));
    std::cout << "*sp1 = " << *sp1 << std::endl;
 
    SmartPtr<int[]> sp2(new int[3]{1, 2, 3});
    std::cout << "sp2[1] = " << sp2[1] << std::endl;
 
    return 0;
}

템플릿 특수화와 오버로딩의 차이

 템플릿 특수화와 함수 오버로딩은 다른 개념입니다.

  1. 특수화 : 기존 템플릿의 특정 인스턴스에 대한 다른 구현 제공
  2. 오버로딩 : 같은 이름의 다른 함수 정의
예제
// 예제
#include <iostream>
#include <cstring>
 
// 일반 템플릿
template <typename T>
void print(T value) {
    std::cout << "Template: " << value << std::endl;
}
 
// const char* 에 대한 특수화
template <>
void print<const char*>(const char* value) {
    std::cout << "Specialization for const char*: " << value << std::endl;
}
 
// 오버로딩
void print(int value) {
    std::cout << "Overload for int: " << value << std::endl;
}
 
int main() {
    print(5.5);          // 일반 템플릿 사용
    print("Hello");      // const char* 특수화 사용
    print(10);           // 오버로딩된 함수 사용
    return 0;
}

연습 문제

  1. Array 클래스 템플릿을 구현하고, bool 타입에 대해 특수화하여 각 요소를 1비트로 저장하도록 만드세요.
  2. max 함수 템플릿을 구현하고, C-style 문자열에 대한 특수화를 작성하세요.

 참고자료