C++ 클래스 템플릿 특수화(specialization),구체화(instantiation)

프로그래밍/C++ 2019.10.31 댓글 Plorence

암시적 구체화(implicit instantiation)

이때까지 선언한 게 암시적 구체화를 사용합니다.

템플릿 클래스를 만들게 되면(선언하게 되면) 필요에 따라 컴파일러는 클래스 템플릿(설계도)을 사용하여 하나의 특수화된 클래스 정의를 생성합니다.

컴파일러는 객체가 요구될 때까지 그 클래스의 암시적 구체화를 생성하지 않습니다.

즉 포인터 선언만 하고 객체를 생성하고 그 포인터가 가리킬 때 암시적 구체화가 됩니다.

Array<int> * arr2; //선언만 해서 암시적 구체화가 안됨
arr2 = new Array<int>; //이제 암시적 구체화가 됨

만약 포인터가 아닌 객체라면, 선언만으로도 암시적 구체화가 됩니다.

 

명시적 구체화(explicit instantiation)

암시적 구체화는 컴파일러가 클래스 템플릿을 참고하여 하나의 특수화된 클래스 정의를 생성한다고 했는데

명시적 구체화는 객체가 생성되거나 언급되지 않았어도 그 클래스를 정의합니다.

그 선언은 템플릿 정의와 동일한 이름 공간 안에 있어야 합니다.

#include <iostream>
using namespace std;
template<class Type, class Type2 = double>
class Array {
private:
       Type arr[1000];
       Type2 arr[1000];
public:
};
template class Array<int, int>; //명시적 구체화
int main(void) {
       
};

만약 명시적 구체화를 안쓰고 암시적 구체화도 안됐다면 위 코드의 에러는 발생하지 않습니다.

왜냐하면 클래스 정의를 생성하지 않았기 때문입니다.

코드를 실행하면 에러가 발생하는 걸 알 수 있는데, 명시적 구체화로 인해 클래스의 정의가 생성되었기 때문입니다.

(컴파일 타임에 생성됨)

 

명시적 특수화(explicit specializaion)

명시적 특수화는 사용하려는 특정한 데이터형을 위한 정의 입니다.

때로는 템플릿이 특정형에 맞게 구체화될 때 조금 다르게 행동하도록 수정해야 하는 경우가 있습니다.

예를 들어서 int일 때는 추가로 10을 더해주고 double일 때는 추가로 100.0을 더하고 싶을 때는 명시적 특수화를 해야 합니다.

#include <iostream>
using namespace std;
#include <typeinfo>   
template <typename Type>
class IData {
private:
       Type total;
public:
       IData(Type num1) : total(num1) {
       }
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
template<>
class IData<int> { //명시적 특수화
private:
       int total;
public:
       IData(int num1) : total(num1) {
       }
       void Add(int num1) {
              total += num1+10;
       }
       void Show() const {
              cout << "타입:" << typeid(int).name() << " 토탈:" << total <<  std::endl;
       }
};
int main(void) {
       IData<int> data_int = IData<int>(100);
       data_int.Show();
       data_int.Add(10);
       data_int.Show();
       IData<double> data_double = IData<double>(100);
       data_double.Show();
       data_double.Add(10);
       data_double.Show();
};
타입:int 토탈:100
타입:int 토탈:120
타입:double 토탈:100
타입:double 토탈:110

명시적 특수화할 때 구형 컴파일러는 인식 못하는 경우가 있습니다.

그럴 때는 template <>을 빼주면 됩니다.

//template<>
class IData<int> {

우선순위는 명시적 특수화된 정의를 사용하고 명시적 특수화된 정의가 없으면 포괄적인 정의를 사용합니다.

이게 무슨 말이냐면 지금 int에 대한 명시적 특수화를 했습니다.

그럼 객체 선언할 때 데이터형을 int로 전달했을 때 명시적 특수화된 정의를 사용하게 됩니다.

그래서 Add메서드를 호출할 시에 추가로 10을 더해줍니다.

이때 특수화를 한다면 템플릿 매개변수의 개수는 반드시 일치해야 합니다.

 

부분적인 특수화(partial specialization)

부분적인 특수화는 템플릿의 포괄성을 일부 제한합니다.

데이터형 매개변수 중 어느 하나에 구체적인 데이터형을 제공할 수 있습니다.

#include <iostream>
using namespace std;
#include <typeinfo>   
template <typename Type,typename Type2>
class IData {
private:
       Type total;
public:
       IData(Type num1) : total(num1) {
       }
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
template<typename Type>
class IData<Type,int> { //명시적 부분적인 특수화
private:
       int total;
public:
       IData(int num1) : total(num1) {
       }
       void Add(int num1) {
              total += num1+10;
       }
       void Show() const {
              cout << "타입:" << typeid(int).name() << " 토탈:" << total <<  std::endl;
       }
};
타입:int 토탈:100
타입:int 토탈:120
타입:double 토탈:100
타입:double 토탈:110

부분적인 특수화에서도 앞서 말한 우선순위가 적용됨을 알 수 있습니다.

 

또는 포인터들을 위한 특별한 버전을 제공할 수 있습니다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <typeinfo>   
template <typename Type>
class IData {
private:
       Type total;
public:
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
template <class Type *> //명시적 특수화
class IData {
private:
       Type total;
public:
       
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
int main(void) {
       IData<int> arr1; //포괄적인 템플릿을 사용한다.
       IData<int *> arr2; //Type * 특수화를 사용한다.
};

(그냥 이렇게 된다는 거만.. 멤버 관련은 설계하지 않았습니다.)

포인터를 위한 부분적인 특수화를 할 때는 typename이 아닌 반드시 class를 써주어야 합니다.(VS 2017) 기준

 

멤버 템플릿

클래스 템플릿은 구조체, 내포된 템플릿 클래스, 템플릿 클래스의 멤버를 가질 수 있습니다.

class IData {
private:
       Type total;
       template <class Type1>
       class IData_P { //내포된 템플릿 클래스 멤버
       private:
              Type1 total;
       public:
              void Add(Type1 num1) {
                     total += num1;
              }
              void Show() const {
                     cout << "타입:" << typeid(Type1).name() << " 토탈:" << total  << std::endl;
              }
       };
       IData_P<Type> In; //템플릿 객체
public:
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
       template<typename T>
       decltype(auto) Add(T n1, Type n2) {
              return n1 + n2;
       }
};

IData_P 클래스 템플릿은 IData 클래스 사용 범위 내에서만 접근할 수 있습니다. 

멤버 In은 Type형에 기초하는 IData_P객체입니다.

IData<int> data;

위 객체 선언은 IData 템플릿의 private 멤버인 In을 IData_P <int>로 만듭니다.

어떤 컴파일러들은 템플릿 멤버를 전혀 받아들이지 못하는 것도 있고 클래스 바깥에 정의를 두는 것을 허용하지 않는 경우도 있습니다.

 

Add(T, Type) 멤버 함수의 경우 그 멤버 함수가 호출될 때 매개변수의 값에 의해 암시적으로 결정되는 데이터형(T)와 그 객체가 구체화 데이터형에 의해 결정되는 데이터형(Type)하나 가집니다.

IData<int> data_int = IData<int>();

위 객체가 구체화되면 해당 멤버 함수의 매개변수 n2는 int형이 됩니다.

template<typename T>
decltype(auto) Add(T n1,int n2) {
       return n1 + n2;
}

 

T는 호출될 때 매개변수에 의해 결정됩니다.

 

내포된 클래스 템플릿을 바깥에 정의를 했을 경우에

#include <iostream>
using namespace std;
#include <typeinfo>   
template <typename Type>
class IData {
private:
       Type total;
       template<typename Type1> //선언
       class IData_P;
       IData_P<Type> In; //템플릿 객체
public:
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
       template<typename T>
       decltype(auto) Add(T n1, Type n2);
};
template <typename Type>
template <class Type1>
class IData<Type>::IData_P{ //내포된 템플릿 클래스 멤버
private:
       Type1 total;
public:
       void Add(Type1 num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type1).name() << " 토탈:" << total <<  std::endl;
       }
};
template <typename Type>
template<typename T>
decltype(auto) IData<Type>::Add(T n1, Type n2) { //템플릿 멤버 함수
        return n1 + n2;
}

 

매개변수 템플릿

매개변수 템플릿은 매개변수로 클래스 템플릿도 받을 수 있습니다.

표준 템플릿 라이브러리를 구현하기 위해 최근에 추가된 템플릿 기능입니다.

#include <iostream>
using namespace std;
template <typename Type, template <typename type> class temp_c>
class IData {
private:
       Type total;
public:
       temp_c<Type> var;
       IData(int num1) : var(num1) {
       }
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
template <typename type>
class Var {
       type var;
public:
       Var(type p_var) : var(p_var) {
       }
       void Add(type p_var) {
              var += p_var;
       }
       void Show() const {
              cout << var;
       }
};
int main(void) {
       IData<int,Var> Idata(10);
};

여기서 template <typename type> class가 데이터형이고 temp_c가 매개변수입니다.

만약 클래스 템플릿 Var를 전달하면 IData 클래스 템플릿 내부에서는 temp_c가 Var로 대체될 것입니다.

Var<int> var;

위와 같은 객체가 선언됩니다.

이때 매개변수 템플릿에서의 템플릿 매개변수 개수템플릿 클래스의 매개변수 개수가 일치해야 합니다.

마찬가지로 타입도 일치해야 합니다.

#include <iostream>
using namespace std;
template <typename Type, template <typename type,int n> class temp_c>
class IData {
private:
       Type total;
public:
       temp_c<Type> var;
       IData(int num1) : var(num1) {
       }
       void Add(Type num1) {
              total += num1;
       }
       void Show() const {
              cout << "타입:" << typeid(Type).name() << " 토탈:" << total <<  std::endl;
       }
};
template <typename type>
class Var {
       type var;
public:
       Var(type p_var) : var(p_var) {
       }
       void Add(type p_var) {
              var += p_var;
       }
       void Show() const {
              cout << var;
       }
};
int main(void) {
       IData<int,Var> Idata(10); //에러 발생
};

댓글