Programming Language/C++

[C++] 템플릿 특수화와 비타입 파라미터

lumana 2024. 6. 28. 20:24

템플릿 특수화와 비타입 파라미터

템플릿 특수화

  • 지난 시간에 함수 템플릿을 다루면서 아래 코드를 봤을 거에요
#include <iostream>
#include <string>

using namespace std;

template <typename T>
T getArraySum(const T arr[], int n) {
    T sum = arr[0];
    for (int i = 1; i < n; i++) {
        sum += arr[i];
    }
    return sum;
}

int main() {
    string sarr[3] = { "hello", "world", "luna" };
    string ssum = getArraySum(sarr, 3);
    cout << ssum << endl;
}
  • ssumd에서 hello, world, luna 사이에 공백을 넣어주고 싶다.

    • 이는 일반적인 함수 템플릿으로는 불가능하다
  • 템플릿 특수화를 통해서 템플릿 파라미터에 특정 타입이 들어왔을 때 다른 식으로 특수하게 작동하도록 만들어 줄 수 있다.

#include <iostream>
#include <string>

using namespace std;

template <typename T>
T getArraySum(const T arr[], int n) {
    T sum = arr[0];
    for (int i = 1; i < n; i++) {
        sum += arr[i];
    }
    return sum;
}

// 템플릿 특수화
template<>
string getArraySum<string>(const string arr[], int n) {
    string sum = arr[0];
    for (int i = 1; i < n; i++) {
        sum += ' ' + arr[i];
    }
    return sum;
}

int main() {
    string sarr[3] = { "hello", "world", "luna" };
    string ssum = getArraySum(sarr, 3);
    cout << ssum << endl;
}
  • template 인자를 담는 꺽쇠 <> 사이에 아무것도 안적고, 아래에 함수 타입을 적어주면 해당 타입에 대해 특수화를 해주겠다는 의미이다.

  • 특수화를 하게 되면, 특수화를 한 타입에 대해서는 특수화를 한 함수로 작동한다

  • int에 대해서 작동하도록 추가해서 직접 비교해보자


int main() {
    string sarr[3] = { "hello", "world", "luna" };
    string ssum = getArraySum(sarr, 3);
    cout << ssum << endl;

    int iarr[5] = { 3, 1, 4, 1, 5};
    int isum = getArraySum(iarr, 5);
    cout << isum << endl;
}

비타입 파라미터

  • c++에서 강력한 기능 중 하나

  • 지금까지는 타입 파라미터만 사용해왔다.

    • tempalte
  • 타입이 아닌 일반적인 숫자 같은 것도 넣어줄 수 있다.

    • template
  • 타입 파라미터와 비타입 파라미터를 섞어서 사용할 수도 있다

Example) Vector 클래스

#include <iostream>
using namespace std;

template <typename T>
class Vector {
   public:
    Vector(int n) {
        this->n = n;
        comp = new T[n];
    }
    ~Vector() {
        delete[] comp;
    }
    T GetComp(int i) const { // i 번째 성분을 리턴
        return comp[i];
    }

    void SetComp(int i, T val) {
        comp[i] = val;
    }

    Vector operator+(const Vector &rhs) const {

    }

   private:
    int n;
    T *comp;  // 벡터의 성분
};

int main() {
    Vector<float> v(3);
    v.SetComp(0, 2);
    v.SetComp(1, 3);
    v.SetComp(2, 4);
    cout << v.GetComp(0) << ", "<< v.GetComp(1) << ", " << v.GetComp(2) << endl;
        // (1, 2) + (3, 4, 5)
}
  • (1, 2) + (3, 4, 5)와 같이 다른 차원의 덧셈을 할 때, operator+ 연산자 함수가 실행되지 않도록 일일히 사용자가 컨트롤해줘야 하는 불편함이 생긴다

  • 이 때 비타입 파라미터로 차원을 받아보자

#include <iostream>
using namespace std;

template <typename T, int n>
class Vector {
   public:
    Vector() {
        comp = new T[n];
    }
    ~Vector() {
        delete[] comp;
    }
    T GetComp(int i) const { // i 번째 성분을 리턴
        return comp[i];
    }

    void SetComp(int i, T val) {
        comp[i] = val;
    }

    Vector operator+(const Vector &rhs) const { // Vector<T, n>이 생략된 것
                Vector res;
        for (int i = 0; i < n; i++) {
            res.SetComp(i, this->GetComp(i) + rhs.GetComp(i));
        }
        return res;
    }

   private:
    T *comp;  // 벡터의 성분
};

int main() {
    Vector<float, 3> v1, v2;
    Vector<float, 2> v4;
    v1.SetComp(0, 2);
    v1.SetComp(1, 3);
    v1.SetComp(2, 4);

    v2.SetComp(0, 5);
    v2.SetComp(1, 6);
    v2.SetComp(2, 7);

    // 아래는 오류 발생. 타입이 다르기 때문
    // Vector<float, 3> v3 = v1 + v4;
    Vector<float, 3> v3 = v1 + v2;
    cout << v3.GetComp(0) << ", "<< v3.GetComp(1) << ", " << v3.GetComp(2) << endl;
}
  • 이렇게 해주면 this와 rhs의 차원이 같다는 것이 보장된다

  • Why?

    • += 연산자에서 Vector는 Vector<T, n>이 생략된 것이다.

    • Vector<float, 2>와 Vector<float, 3>은 타입 자체가 다르기 때문에 다른 차원을 +=연산자의 매개변수로 받을 수 없게 된다

  • 위 연산자에 문제점 하나가 있다.

    • 임시 객체가 생기고 반환된다. 이 때 얕은 복사가 일어나 문제가 생긴다.

    • 이전에 우리가 복사 대입 연산자와 복사 생성자를 통해서 이런 문제를 해결한 적이 있었다.

    • 만들어 주기 전에 아래 내용을 먼저 살펴보자

  • template<typename T, int n>에서 n이라는 숫자는 타입의 일부이다.

    • 그런데 타입의 일부를 클래스 안에서 그냥 사용할 수 있다
  • 비타입 파라미터를 사용하지 않는 코드

template <typename T>
class Vector {
   /* 생략 */
   private:
      /* 오류가 발생한다
        int n;
    T comp[n];  // 벡터의 성분
        */
};
  • n이 멤버 변수면 에러가 발생한다.

    • Why?

    • 클래스가 정의되기 위해서는 클래스의 크기를 알아야 한다.

    • 만약 클래스의 멤버중에 float comp[n]; 이라는게 있다면, n이 상황에 따라 달라질 수 있다. 따라서 배열의 크기를 클래스 안에 저장하는 것은 불가능하다. (동적인 시간에 동적할당을 사용하지 않고 배열의 크기를 정하는 것은 불가능)

  • 만약 비타입 파라미터를 사용한다면?

#include <iostream>
using namespace std;

template <typename T, int n>
class Vector {
   /* 생략 */
   private:
    T comp[n];  // 벡터의 성분
};

int main() {
    Vector<float, 3> v1, v2;
    Vector<float, 2> v4;
  • v1, v2에서 T에는 float가, n에는 3이 들어가서, Vector<float, 3> 타입이 된다 --> float comp[3] --> 12바이트

  • v4에서 T에는 float가, n에는 2이 들어가서, Vector<float, 2> 타입이 된다 --> float comp[2] --> 8바이트

  • comp 배열의 크기가 달라도, 애초에 타입의 크기가 다르기 때문에 문제가 없다

    • 정적인 시간에 정해지기 때문에 동적할당에 대한 걱정도 할 필요가 없다

참조) 두들낙서 C/C++ 강좌