IT Language 연습실
template (4) 클래스 ver 본문
클래스 템플릿 기반으로 컴파일러가 만든 템플릿 클래스의 객체를
또다른 클래스 템플릿 기반으로 컴파일러가 만든 템플릿 클래스가 받기.
위가 말하는 것은
#include <iostream>
template <typename T>
class A {
T x; T y;
public :
A(T n = 0, T m = 0) : x(n), y(m) { }
void show() {
std::cout<<x<<' ' <<y<<std::endl;
}
};
template<typename T>
class B {
T *ptr ;
int len ;
public :
B(int n) : len(n) {
ptr = new T[len]; // A<int>[len]
}
T & operator[](int n) {
return ptr[n];
}
~B() { delete ptr; }
};
int main() {
B <A<int>> aa(3) ;
aa[0] = A<int>(3,4);
aa[1] = A<int>(5,6);
aa[2] = A<int>(7,8);
for(int i=0; i<3; i++) {
aa[i].show();
}
}
위 코드와 같다.
B <A<int>> aa(3) ;
가장 핵심이 되는 코드인데.
A 클래스 템플릿을 정의하였고 그렇게 만들어진 A 클래스 템플릿을 기반으로 컴파일러는
A 정수 템플릿 클래스를 생성하여 그 템플릿 클래스를 B 클래스의 자료형 타입의 템플릿 클래스를 생성한 것이다.
즉,
class B {
A<int> *ptr ;
int len ;
public :
B(int n) : len(n) {
ptr = new A<int>[len]; // A<int>[len]
}
A<int> & operator[](int n) {
return ptr[n];
}
~B() { delete ptr; }
};
와 같다는 것이다. <int>가 붙어서 그렇지 결국은 A클래스를 반환하고 A클래스의 객체를 배열로 해서 동적으로 만들고
물론 double 이라면
B <A<double>> ab(3) ;
ab[0] = A<double>(3.3,4.3);
ab[1] = A<double>(5.3,6.3);
ab[2] = A<double>(7.3,8.3);
for(int i=0; i<3; i++) {
ab[i].show();
}
다음과 같이 메인 영역을 작성할 것이고 그렇다며 A의 템플릿 클래스를 컴파일러가 만들때
class A {
double x; double y;
public :
A(double n = 0, double m = 0) : x(n), y(m) { }
void show() {
std::cout<<x<<' ' <<y<<std::endl;
}
};
이렇게 작성되어 이 double 형의 템플릿 클래스를 B클래스의 자료형 타입으로 하여 사용하겠다. 라는 의미가 된다.
이것이 클래스 템플릿 기반으로 컴파일러가 만든 템플릿 클래스의 객체를 또다른 클래스 템플릿 기반으로 컴파일러가 만든 템플릿 클래스가 받기. 이다.
--------- --------- --------- --------- --------- --------- --------- --------- --------- --------- --------- --------- ---------
클래스 템플릿의 특수화
함수 템플릿 기반으로 컴파일러가 생성한 템플릿 함수에서 특수화라는 것을 알아봤다.
예외적으로 함수 템플릿에서 정의된 기능 말고 다른 기능을 사용한다거나. 또는 특정 자료형에 대해서 컴파일러가 생성된 템플릿 말고 사용자가 직접 정의한 기능을 사용할 수 있도록 예외 처리하는 것. 그것이 특수화 작업이다.
template <> 하고 사용했었는데
이를 클래스에서도 사용할 수 있다는 말이다.
#include <iostream>
template <typename T>
void function (T n, T m) {
std::cout<<n<<' '<<m<<std::endl;
}
template <>
void function<int>(int n, int m) {
std::cout<<n+m<<std::endl;
}
template <typename T>
class A{
T x; T y;
public :
A(T n, T m) : x(n), y(m) { std::cout<<x<<' '<<y<<std::endl; }
};
template <>
class A <int> {
int num; int num2;
public :
A (int n, int m) : num(n), num2(m)
{ std::cout<< num + num2 <<std::endl; }
};
int main( ) {
function<int>(3,4);
function<double>(3.3,4.4);
A <int>(3,4);
A <double>(3.3,4.4);
}
모두 같은 결과가 나온다. int를 우리가 따로 정의를 하여 전혀 다른 기능 단순 출력이 아니라 덧셈 연산을 진행한 후 출력
을 진행하고 있는 결과가 나오게 된다. 이러한 작업을 바로 함수와 클래스를 통털어 특수화라고 말한다.
더 세분화해서 말하면 "전체특수화" 라고 말한다.
전체 특수화라는 말이 등장했다면 부분적으로 특수화도 진행할 수 있다.
다음 코드를 살펴보자.
#include <iostream>
template <typename T1, typename T2>
class B {
T1 x; T2 y;
public :
B(T1 n, T2 m) : x(n), y(m) { std::cout<< "템플릿 클래스 생성!! "<<std::endl; }
};
template <>
class B <int,double> {
int x; double y;
public :
B(int n, double m) : x(n), y(m) { std::cout<<"전체 특수화"<<std::endl; }
};
template <typename T1>
class B <T1, double> {
T1 x; double y;
public :
B(T1 n, double m) : x(n), y(m) { std::cout<<"부분 특수화"<<std::endl; }
};
int main( ) {
B <int, int> aa1(10, 10);
B <int, double> aa2(10, 1.1);
B <long, double> aa3(10, 1.1);
B <double, int> aa4(1.1, 10);
}
예를 들어 위와 같은 코드가 있는데
template <typename T1, typename T2>
위와 같이 T1과 T2가 사용되고 있다.
이 자료형에 대해서 T1은 적용을 하지 않고 T2에 대해서는 double로 자료형을 둔다면 = 부분 특수화
이 자료형에 대해서 T1도 int 혹은 double 등등 으로 적용하고 T2도 double로 자료형을 둔다면 = 전체 특수화
부분 특수화와 전체 특수화에 차이는
1) 전체특수화는 <> 을 사용하면서 컴파일러가 새로운 템플릿 (함수, 클래스)에 대해서 생성하지 않는다.
부분특수화는 template <typename T1> 을 또 한번 사용하면서
T2는 자료형이 정의됐고 정의된 자료형에 맞고 T1에는 자료형이 없는 템플릿을 새롭게 생성하는 것 같다?
2) 메인 부분에서 <int, double> 을 보면 전체 특수화와 부분 특수화 모두 조건을 만족한다.
이럴경우 전체 특수화가 더 우선시 처리된다.
즉 정리하면
예를 들어서 T1과 T2로 자료형이 정의되어 있지 않고 사용됐는데 T1에 대해서만 자료형을 정의했다면
A 클래스 템플릿인데 T1에 대해서만 정의된 클래스 템플릿 기반으로 컴파일러가 템플릿 클래스를 생성.
혹은 A 클래스 템플릿인데 T2에 대해서만 정의된 클래스 템플릿 기반으로 컴파일러가 템플릿 클래스를 생성.
그게 class A <typename t2> or class A <typename T1> 이 될 수 있다.
만약 부분특수화와 전체 특수화가 같이 정의가 되어 있는 상황에서 둘다 조건을 만족하는 상황이라면
전체특수화가 우선적 처리.
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
클래스 템플릿의 매개변수로 변수선언
template <typename T1, typename T2>
이거 좀 더 자세히 살펴보면 typename T1과 typename T2는 템플릿 (클래스, 함수)의 매개변수이다.
#include <iostream>
template <typename T1, int len>
class B {
T1 x;
public :
B(T1 n) : x(n) { std::cout<<x+len<<std::endl; }
};
int main( ) {
B <int, 10> aa1(10);
}
위를 보면 알겠지만.
template <typename T1, int len>
int len이라는 변수가 선언이 되어 있다.
B <int, 10> aa1(10);
<int, 10> int와 10을 인자로 전달하고 있다.
즉, 클래스 템플릿의 매개변수로 변수선언 (매개변수의 변수선언도 템플릿 생성하고 그 생성된 템플릿 클래스를 불러내는데 있어서 영향을 준다는것.)
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
그렇다면 템플릿 매개변수니까. 초기값을 설정해줄 수 있다. 값이 인자로 전달받지 못했다면 말이다.
다음과 같이 말이다.
template <typename T1 = int, int len = 10>
위와 같이 초기값을 설정해줄 수 있고
B < > aa1(10);
위와 같이 어떠한 것도 인자로 전달하지 않을 수 있다.
여기서 차이가 클래스와 함수 템플릿의 또 차이가 보인다.
(템플릿 클래스 객체를 생성할떄도 <> 라는 명시를 해줘야한다는 것.!!) 함수 템플릿 기반의 템플릿 함수와 차이.
근데 여기서 또 궁금함이 있을 것이다.
int len에 대해서는 초기값을 설정해주는데 있어서 생성자를 이용하면 되는 것 아니겠느냐?
맞다. 생성자를 이용해도 상관없다.
하지만 템플릿 클래스를 컴파일러가 생성한다면 자료형에 맞는 템플릿 클래스를 생성한다는 것은 알고 있다
하지만 만일에 int len도 어쨋든 템플릿의 매개변수로 들어간 것이니. 템플릿 클래스를 생성하는데 있어서
영향을 줄 수 밖에 없다.
그럼 더 예를 들어 만일에 배열이라고 생각해보자 .
#include <iostream>
template <typename T1 = int, int len = 0>
class B {
T1 array[len];
public :
B() { }
T1 & operator[](int n) {
return array[n];
}
void operator=(T1 & ref) {
for (int i=0; i<len; i++) {
array[i] = ref.array[i];
}
}
};
int main( ) {
B <int, 3> aa1;
for(int i=0; i<3; i++) {
aa1[i] = (i+1)*10;
}
B <int, 3> aa2;
aa2 = aa1;
B <int, 5> aa3;
for(int i=0; i<5; i++) {
aa3[i] = (i+1)*10;
}
for(int i=0; i<3; i++) {
std::cout<<aa2[i]<<std::endl;
}
/* aa3 = aa1; */
}
위와 같이 int len도 어쨋든 템플릿의 매개변수로 들어간 것이니. 템플릿 클래스를 생성하는데 있어서
영향을 줄 수 밖에 없다는 말은 주석이 된 부분을 실행을 통해 증명됐다.
aa2 와 aa1의 객체는 같은 템플릿 클래스로부터 생성이 됐기 때문에 대입 연산자 오버로딩으로 복사가 가능하다.
하지만 aa3는 aa2, aa1 과 다른 템플릿 클래스로부터 생성됐음을 알 수 있고 그로 인해 오버로딩이 불가능하다.
즉 컴파일러는 int len이라는 녀석도 템플릿 클래스를 생성하고 len의 따라 각각의 템플릿 클래스를 생성한다는 것이다.
즉 aa3 의 len은 5 aa1과 aa2는 3
자료형은 모두 int 인데도 불구하고 len의 값이 달라 서로 다른 템플릿 클래스를 생성했다는 것이다.
그래서 복사할 수 없다는 것이고.
따라서 이를 생성자를 이용하여 초기값을 설정해둔다면 길이에 따라 배열을 복사하고 생성하고 길이가 다르면 그 작업을 못하게 하는 그런 작업들을 또 다로 코드 작성을 통해 해줘야 하며 이는 CPU가 처리해야 하는 것이 더 늘어날뿐이다.
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
템플릿에서 static 사용하기. (함수 템플릿, 클래스 템플릿)
C++ 문서에 static 을 정리한 페이지가 있다 그걸 바탕으로 하는 것이니. 꼭 읽어보고 이해하고 오자!
static 정리한 페이지에 이해가 됐다면 해당 페이지에서 알려주는 static을 이해하는데 어렵지 않을 것이다.
#include <iostream>
template <typename T>
void function() {
static T count;
count++;
std::cout<<count<<std::endl;
}
int main() {
function<int>();
function<int>();
function<double>();
function<double>();
}
다음의 코드가 있다. 다음의 코드는 함수 템플릿을 정의하고 컴파일러가 템플릿 함수를 생성한다.
근데 static이 있는데 자료형에 따른 static 으로 가고 있다.
실행결과를 보면 알 수 있지만 int 와 double 각각의 static count라는 변수를 갖고 있다.
static도 각각 생성됨을 알 수 있다.
#include <iostream>
template <typename T>
class A {
static T count;
public :
A() {
count++;
std::cout<<count<<std::endl;
}
};
template <typename T>
T A<T>::count = 0;
int main() {
A <int> aa;
A <int> aa2;
A <double> ab;
}
클래스의 static 부분에서도 각각 static이 자료형에 맞는 객체 생성에 따라 템플릿 클래스 생성에 따라
static의 값이 다르다는 것을 알 수 있다.
그렇기에 static의 값을 초기화 하는 부분에서 int 는 0부터 double 부분은 5부터 시작하도록 값을 설정해줄 수 있다.
template <>
double A<double>::count = 5;
로 사용하면 된다.
Tip이 있는데
어느때 template <> 을 쓰고 어느때 template <typename T> 을 써야 하는지 모르겠다? 라고 생각할 수 있다.
정확히 말하면
template <typename T>는 같은 기능을 수행하는 클래스나 함수들을 자료형에 따라서 처리하도록 하게 하고 싶다 즉, 자료형에 맞게 여러번 정의할 필요없이 하나의 정의로 자료형에 맞는 처리를 하고 싶다 (클래스, 함수) 템플릿을 사용하고
그 자료형에 맞는 템플릿을 컴파일러가 생성하기 위해서 T 가 무엇인지 컴파일러에게 명시해줘야 한다.
그렇기 때문에 template <typename T>를 사용하는 것이다.
template <> 은 예외적으로 템플릿에서 정의한 기능말고도 다른 기능을 추가한다던지 다른 기능을 수행한다던지
어떤 특정 자료형에 대해서는 컴파일러야 새롭게 템플릿을 만들지말고 우리가 정의한걸로 사용해라고 명시하는 거기에
T라는 것도 사용하지 않을 뿐더러 자료형을 명시해준다 int면 int double이면 double 그렇기 때문에
특수화 작업을 위해서 사용하는 것이다.
그렇기 떄문에 좀 음.. 편하게? 물론 위 항목들을 이해하고 있다면 더 좋겠지만 떄려죽어도 모르겠다라면.. 어쩔 수 없지만
T 같은 걸 사용했다 그럼 template <typename T> 라고 명시를 해주고
T 같은 걸 사용하지 않았다 template <> 을 사용한다.
라고 알아두고 다시 이해하려고 노력해야 한다.
'C++' 카테고리의 다른 글
C++ 공부하면서 정리한 것들 (0) | 2024.02.25 |
---|---|
C++ 스타일에 맞는 형변환 (0) | 2024.02.25 |
스마트 포인터 (Smart Pointer) (0) | 2024.02.22 |
template 파일 분할. (0) | 2024.02.22 |
template (3) 클래스 ver (0) | 2024.02.22 |