본문 바로가기
Programming/C++

템플릿2

by OKOK 2017. 8. 1.

C++ 표준 라이브러리는 템플릿을 기반으로 디자인된다. 따라서 템플렛을 잘 알면, 그만큼 라이브러리에 대한 이해도와 활용능력이 향상됩니다. Point 클래스를 다음의 형태로 템플릿화 하였습니다. BoundCheckArray 클래스 템플릿을 정의하였습니다. 위으 클래스 템플릿을 기반으로 Point<int> 템플릿 클래스의 객체를 저장할 수 있는 객체는 어떻게 생성해야 할까? 템플릿 클래스라 하여 일반 클래스와 비교해서 다른 문법체계를 지니는 것은 아니니, 우리가 알고 있는 C++ 문법의 이해를 기반으로 템플릿과 관련된 다양한 확장이 가능합니다. 


특정 템플릿 클래스의 객체를 인자로 받는 일반함수의 정의와 friend 선언


함수 템플릿을 특수화하는 이유는 특정 자료형에 대해서 구분이 되는 다른 행동을 보이기 위해서 입니다. 클래스 템플릿을 특수화하는 이유는 특정 자료형을 기반으로 생성된 객체에 대해, 구분이 되는 다른 행동양식을 적용하기 위해서입니다. 즉, 클래스 템플릿을 특수화하면, 템플릿을 구성하는 멤버함수의 일부 또는 전부를 다르게 행동하도록 정의할 수 있습니다. 


#pragma warning(disable:4996)

#include <iostream>

#include <cstring>

using namespace std;


template <typename T>

class Point

{

private:

T xpos, ypos;

public:

Point(T x=0, T y=0): xpos(x), ypos(y)

{ }

void ShowPosition() const

{

cout << '[' << xpos << ", " << ypos << ']' << endl;

}

};


template <typename T>

class SimpleDataWrapper

{

private:

T mdata;

public:

SimpleDataWrapper(T data) : mdata(data)

{ }

void ShowDataInfo(void)

{

cout << "Data: " << mdata << endl;

}

};


template <>

class SimpleDataWrapper <char*>

{

private:

char* mdata;

public:

SimpleDataWrapper(char* data)

{

mdata = new char[strlen(data) + 1];

strcpy(mdata, data);

}

void ShowDataInfo(void)

{

cout << "String: " << mdata << endl;

cout << "Length: " << strlen(mdata) << endl;

}

~SimpleDataWrapper() { delete[]mdata; }

};


template <>

class SimpleDataWrapper <Point<int>>

{

private:

Point<int> mdata;

public:

SimpleDataWrapper(int x, int y) : mdata(x, y)

{ }

void ShowDataInfo(void)

{

mdata.ShowPosition();

}

};


int main(void)

{

SimpleDataWrapper<int> iwrap(170);

iwrap.ShowDataInfo();

SimpleDataWrapper<char*> swrap("Class Template Sepcialization");

swrap.ShowDataInfo();

SimpleDataWrapper<Point<int>> poswrap(3, 7);

poswrap.ShowDataInfo();

return 0;


클래스 템플릿 SimpleDataWrapper가 정의되었습니다. 이는 간단히 하나의 데이터를 저장하고, 이 데이터에 담긴 정보를 출력하도록 정의하였습니다. 클래스 템플릿 SimpleDataWrapper를 char* 형에 대해서 특수화하였습니다. 이는 문자열을 저장하기 위한 것으로써, 이를 목적으로 동적할당 기반의 생성자와 소멸자를 별도로 정의하였으며, 데이터의 출력을 진행하는 ShowDataInfo 함수에서는 문자열의 길이정보도 함께 출력하도록 정의하였습니다. 


char* 형과 템플릿 클래스의 자료형인 Point<int> 형 대상의 특수화를 보이고 있습니다. 클래스 템플릿의 부분 특수화


#include <iostream>

using namespace std;


template <typename T1, typename T2>

class MySimple

{

public:

void WhoAreYou()

{

cout << "size of T1: " << sizeof(T1) << endl;

cout << "size of T2: " << sizeof(T2) << endl;

cout << "<typename T1, typename T2>" << endl;

}

};


template <>

class MySimple<int, double>

{

public:

void WhoAreYou()

{

cout << "size of int: " << sizeof(int) << endl;

cout << "size of double: " << sizeof(double) << endl;

cout << "<int, double>" << endl;

}

};


/*

template <typename T1>

class Mysimple<T1, double>

{

public:

void WhoAreYou()

{

cout<<"size of T1: "<<sizeof(T1)<<endl;

cout<<"size of double: "<<sizeof(double)<<endl;

cout<<"<<T1, double>"<<endl;

}

};

*/


int main(void)

{

MySimple<char, double> obj1;

obj1.WhoAreYou();

MySimple<int, long> obj2;

obj2.WhoAreYou();

MySimple<int, double> obj3;

obj3.WhoAreYou();

return 0;


반면 주석을 해제하면, T1, double 에 대해서 부분 특수화가 진행됩니다. 따라서 T2에 double을 지정해서 객체를 생성하면, T1, double에 대해 부분적으로 특수화된 클래스의 객체가 생성됩니다. 


템플릿 인자


템플릿 매개변수에는 변수의 선언이 올 수 있습니다. 이렇듯 템플릿 매개변수에도 변수가 올 수 있습니다. 이를 기반으로 다음의 형태로 객체생성이 가능합니다. 위에서 보인 것처럼 배열의 길이를 결정하기 위해서 굳이 템플릿 매개변수에 정수를 전달하는 피곤한 작업까지는 하지 않아도 될 것 같은데요. 생성자를 이용하면 되잖아요. 


#include <iostream>

using namespace std;


template <typename T, int len>

class SimpleArray

{

private:

T arr[len];

public:

T& operator[] (int idx) { return arr[idx]; }

SimpleArray<T, len>& operator=(const SimpleArray<T, len>& ref)

{

for (int i = 0; i < len; i++)

arr[i] = ref.arr[i];

return *this;

}

};


int main(void)

{

SimpleArray<int, 5> i5arr1;

for (int i = 0; i < 5; i++)

i5arr1[i] = i * 10;


SimpleArray<int, 5> i5arr2;

i5arr2 = i5arr1;

for (int i = 0; i < 5; i++)

cout << i5arr2[i] << ", ";

cout << endl;


SimpleArray<int, 7> i7arr1;

for (int i = 0; i < 7; i++)

i7arr1[i] = i * 10;


SimpleArray<int, 7> i7arr2;

i7arr2 = i7arr1;

for (int i = 0; i < 7; i++)

cout << i7arr2[i] << ", ";

cout << endl;

return 0;


서로 다른 형입니다. 때문에 길이가 다른 두 배열 객체간의 대입은 허용되지 않습니다. 즉, 다음의 대입연산에서는 컴파일 에러가 발생합니다. 


템플릿 매개변수는 디폴트 값 지정도 가능합니다.

#include<iostream>

using namespace std;


template <typename T=int, int len=7>

class SimpleArray

{

private:

T arr[len];

public:

T& operator[] (int idx) { return arr[idx]; }

SimpleArray<T, len>& operator=(const SimpleArray<T, len>&ref)

{

for (int i = 0; i < len; i++)

arr[i] = ref.arr[i];

return *this;

}

};


int main(void)

{

SimpleArray<> arr;

for (int i = 0; i < 7; i++)

arr[i] = i + 1;

for (int i = 0; i < 7; i++)

cout << arr[i] << " ";

cout << endl;

return 0;


T에 int가 len에 7이 디폴트 값으로 지정되었습니다. 디폴트 값을 이용한 객체의 생성을 보이고 있습니다. 



템플릿과 static


딱 한번 초기화된 상태에서 그 값을 계속 유지하는 static 변수의 특성을 이미 잘 알고 있다고 가정하고 설명을 진행하겠습니다. 

#include <iostream>

using namespace std;


template <typename T>

void ShowStaticValue(void)

{

static T num = 0;

num += 1;

cout << num << " ";

}


int main(void)

{

ShowStaticValue<int>();

ShowStaticValue<int>();

ShowStaticValue<int>();

cout << endl;

ShowStaticValue<long>();

ShowStaticValue<long>();

ShowStaticValue<long>();

cout << endl;

ShowStaticValue<double>();

ShowStaticValue<double>();

ShowStaticValue<double>();

return 0;


실행결과는 컴파일러에 의해서 만들어진 템플릿 함수 별로 static 지역변수가 유지됨을 보이고 있습니다. 


클래스 템플릿과 static 멤버변수

static 멤버변수는 변수가 선언된 클래스의 객체간 공유가 가능한 변수입니다. 

#include <iostream>

using namespace std;


template <typename T>

class SimpleStaticMem

{

private:

static T mem;

public:

void AddMem(int num) { mem += num; }

void Showmem() { cout << mem << endl; }

};


template <typename T>

T SimpleStaticMem<T>::mem = 0;


int main(void)

{

SimpleStaticMem<int> obj1;

SimpleStaticMem<int> obj2;

obj1.AddMem(2);

obj2.AddMem(3);

obj1.Showmem();

SimpleStaticMem<long> obj3;

SimpleStaticMem<long> obj4;

obj3.AddMem(100);

obj4.Showmem();

return 0;


템플릿 내에 선언된 static 멤버의 초기화 문장이 어떻게 구성되었는지 이해하였는가? 템플릿 관련 정이에는 template<typename T> 또는 template<>와 같은 선언을 둬서, 템플릿의 일부 또는 전부를 정의하고 있다는 사실을 컴파일러에게 알려야 합니다. 정의 부분에 T가 존재하면 T에 대해 설명을 위해서 <typename T>의 형태로 덧붙이면 되고, T가 존재하지 않으면 <>의 형태로 간단하게 선언하면 됩니다. 


템플릿 static 멤버 변수 초기화의 특수화


특수화는 함수 템플릿 또는 클래스 템플릿만을 대상으로 진행할 수 있는 것이 아닙니다. 클래스 템플릿 정의의 일부인 초기화문을 대상으로도 진행이 가능합니다. 앞서 보인 다양한 특수화와 마찬가지로 T를 대신해서 특수화하고자 하는 자료형의 이름을 삽입하면 됩니다.