ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [C++] 템플릿
    C++/이것이 C++이다 2021. 10. 26. 17:22

    템플릿은 모양을 찍어내는 틀과 같습니다.

    Template
     1. [명사] 형판(形板)
     2. [명사] 견본, 본보기

    C++에서의 템플릿도 위와 유사한 의미로 사용됩니다.

    한 코드를 찍어내듯이 가져다 사용하는 템플릿은, C++의 생산성에 많은 기여를 하는 문법입니다.

    이번 글에서는 템플릿을 사용하는 방법에 대해 살펴볼 것 입니다.

     


     

    1. 템플릿

     

    더보기

    서론에서 템플릿을 틀에 비유했습니다.

    틀로 찍어내는 것은 바로 자료형 (클래스) 입니다.

    템플릿은 함수, 클래스에 적용할 수 있으며 다음과 같은 문법을 사용합니다.

    template<typename [Name]>
    [Return type] [function name]([Params]) { }
    
    template<typename [Name]>
    class [Class name] { };

    관련된 예제와 함께 보겠습니다.

    #include <iostream>
    using namespace std;
    
    template<typename T>
    T add(T a, T b) {
        return a + b;
    }
    
    template<typename T>
    class MyClass {
    public:
        MyClass(T param) : data(param) {}
        T get_data() const { return data; }
        operator T() { return data; }
    private:
        T data;
    };
    
    int main() {
        MyClass<int> a(10);
        MyClass<const char*> b("Hello");
        
        cout << a << "\n";
        cout << b << "\n";
        cout << add<int>(1, 2) << "\n";
    
        return 0;
    }
    10
    Hello
    3

    위의 예제와 같이, 한 클래스 정의로 int, char*에 대응하는 인스턴스를 생성했습니다.

    템플릿을 이용하면 이렇게 상황에 따라 알맞는 인스턴스를 생성할 수 있습니다.

     

    typename은 여러 개가 되거나, 기본 형식이 정해지거나, 다른 형식으로 사용할 수도 있습니다.

    관련된 예제를 살펴보겠습니다.

    #include <iostream>
    using namespace std;
    
    template<typename T = int>
    T add(T a, T b) {
        return a + b;
    }
    
    template<typename T1, typename T2>
    void print(T1 a, T2 b) {
        cout << a << " " << b << "\n";
    }
    
    template<typename T, int val>
    void function(T a) {
        for (int i = 0; i < val; i++) {
            cout << a << " ";
        }
    }
    
    int main() {
        cout << add(1, 2) << "\n";
        print(1.2, "Hello");
        function<const char*, 3>("World");
    
        return 0;
    }
    3
    1.2 Hello
    World World World

    main함수에서 호출된 순서대로 살펴보면

    add함수는 템플릿을 사용했지만 기본 자료형이 정해져 있어서 선언 없이 add(1,2)가 문제없이 작동했습니다.

    print함수는 T1, T2라는 두 개의 템플릿을 사용하여, 각각 double과 char*형의 자료형이 사용되었습니다.

    function함수는 템플릿에 사용된 int val로 반복문을 구성했습니다.

     

    위와 같이, 템플릿은 여러 상황에 알맞게 대응할 수 있도록 프로그램을 작성할 수 있고

    그것이 곧 생산성의 향상으로 이어질 수 있습니다. 

     

    2. 템플릿 특수화

     

    더보기

    위 문단을 통해, 템플릿을 이용해 여러 자료형, 클래스에 대응하는 함수, 클래스를 만들 수 있다는 것을 알게 되었습니다.

    하지만, 특별한 형식에 대하여 다르게 작동해야 하는 기능이 필요할 수 있습니다.

    예를 들어 두 개의 매개변수를 더하는 함수에 대하여, 정수와 실수에 대한 연산과, 문자열에 대한 연산은 서로 달라야 할 것입니다.

    템플릿 특수화는 그런 특수한 상황에 적용되는 문법입니다.

     

    예제를 살펴보겠습니다.

    #include <iostream>
    using namespace std;
    
    template<typename T>
    T add(T a, T b) {
        return a + b;
    }
    
    template<>
    const char* add(const char* a, const char* b) {
        int len_a = strlen(a);
        int len_b = strlen(b);
        char* result = new char[len_a + len_b + 1];
    
        strcpy_s(result, len_a + 1, a);
        strcpy_s(result + len_a, len_b + 1, b);
    
        return result;
    }
    
    int main() {
        cout << add<int>(2, 3) << "\n";
        cout << add<const char*>("Hello,", "World!") << "\n";
    
        return 0;
    }
    5
    Hello,World!

    main함수에서 두번째 호출된 add<const char*>함수는 특수화 된 함수를 호출한 것을 볼 수 있습니다.

    이 함수의 정의부에 typename을 기술하지 않았는데, tempalte<typename T>로 변경해도 문제없이 컴파일됩니다.

    그 외에 주의해야 할 점은, 특수화 된 함수는 원형과 템플릿의 구조가 같아야 한다는 것 입니다.

     

    위 add함수의 경우는 반환 형식, 매개변수 2개가 모두 T로 되어있습니다.

    따라서 특수화하는 함수 또한 반환 형식과 매개변수 2개의 형식이 같아야합니다.

    매개변수를 나누려면 함수 원형의 템플릿의 개수를 늘려주는 식으로 변경할 수 있습니다.

     

    클래스 템플릿 또한 특수화가 가능합니다.

    이 때는, 함수와는 선언 방식이 조금 달라집니다.

     

    예제를 살펴보겠습니다.

    #include <iostream>
    using namespace std;
    
    template<typename T>
    class MyClass {
    public:
        MyClass(T param) : data(param) {}
        T get_data() const { return data; }
    private:
        T data;
    };
    
    template<>
    class MyClass<char*> { // Line 14
    public:
        MyClass(char* param) : data(param) {}
        char* get_data() const { return data; }
    private:
        char* data;
    };
    
    int main() {
        MyClass<int> a(10);
        MyClass<const char*> b("Hello");
    
        cout << a.get_data() << "\n";
        cout << b.get_data() << "\n";
    
        return 0;
    }
    10
    Hello

    Line 14라고 표시된 주석이 char*형식에 대하여 특수화 된 MyClass입니다.

    특수화 하는 타입에 대하여 클래스 선언에 추가해주어야 합니다.

     

    3. 템플릿 상속

     

    더보기

    템플릿 클래스의 상속은 다른 클래스와 다르지 않지만

    추가적으로 선언해야 할 부분이 있기 때문에 가볍게 짚어보도록 하겠습니다.

     

    예제를 살펴보겠습니다.

    #include <iostream>
    using namespace std;
    
    template<typename T>
    class MyClassAnc {
    public:
        MyClassAnc(T param) : data(param) {}
    protected:
        T data;
    };
    
    template<typename T>
    class MyClassDes : public MyClassAnc<T> { // Line 13
    public:
        using MyClassAnc<T>::MyClassAnc;
        T get_data() const { return this->data; }
    };
    
    int main() {
        MyClassDes<int> a(10);
    
        cout << a.get_data() << "\n";
    
        return 0;
    }
    10

     주석처리된 Line 13처럼, 상속 시 템플릿 형식을 같이 선언해주는 것 외에는 달라지는 점이 없습니다.

     


     

    템플릿은 간결하지만 강력한 문법입니다.

    여러 자료형에 대응하는 같은 기능을 만들 때, 템플릿의 사용 여부에 따라서 생산성, 가독성이 달라집니다.

    C++는 Standard Template Libraries (STL, 표준 템플릿 라이브러리)을 제공하는데, 여기에는 여러 알고리즘, 자료구조 등이 템플릿을 통해 구현되어 있습니다.

    이 STL에 대해서도 차후 다룰 수 있도록 하겠습니다.

     

    다음 글은 스마트 포인터 입니다.

    읽어주셔서 감사합니다.

    'C++ > 이것이 C++이다' 카테고리의 다른 글

    [C++] 예외 처리  (0) 2021.11.25
    [C++] 스마트 포인터  (0) 2021.11.03
    [C++] 객체 간 관계  (0) 2021.10.19
    [C++] 다중 상속  (0) 2021.09.24
    [C++] 형 변환 연산자  (0) 2021.09.19

    댓글

Designed by Tistory.