bdfgdfg

[C++] 템플릿 본문

게임프로그래밍/C++

[C++] 템플릿

marmelo12 2022. 1. 16. 18:47
반응형

템플릿은 함수 템플릿과 클래스 템플릿이 존재한다.

함수 템플릿은 함수를 만들어내는 틀이고 클래스 템플릿은 클래스를 만들어내는 틀.

 

void Print(int a, int b)
{
	std::cout << a << b << std::endl;
}

간단하게  위와 같이 정수형 인자를 매개변수로 받는 함수가 있다.

하는일은 단순히 인자로 넘겨준 데이터를 출력해주는일만 한다.

 

만약 여기서 실수,문자,문자열등을 추가로 출력하는 함수를 만든다면 함수 오버로딩을 사용하면 된다.

하지만 이 경우에 내가 정의한 함수들이 누군가가 이 타입도 출력하게끔 요청한다면 그때마다 함수 오버로딩을 추가해야 한다.

 

이 문제는 템플릿을 이용하면 간단하게 해결할 수 있다.

템플릿 함수를 사용하면 컴파일러는 호출측의 함수 호출 인자 타입을 보고 템플릿 함수의 매개변수 타입을 결정한다.

 -> 템플릿 인스턴스 함수를 만들어냄

 

사용하는 방법은 간단하다.

template <typename T>
void Print(T a, T b)
{
	std::cout << a << b << std::endl;
}

int main(void)
{
	Print(10, 20);
	Print(10.5f, 20.7f);
	Print("안녕하세요.", "출력");
	Print('a', 'b');
}

모두 문제없이 작동한다.

 

함수 템플릿 정의는 함수 앞에 template <typename T>라는 키워드를 붙이면 된다.

여기서 T는 템플릿 매개변수명이며 어떤 이름이 붙어도 상관없다.

 

함수 템플릿은 마법처럼 함수 호출 인자로 넘겨준 타입으로 변환하여 실행되는게 아닌,

사용자가 템플릿화 된 함수를 호출할 때 그 타입의 함수를 만들어 낸다.

위의 예제를 예로 들어보면 10,20이라는 정수형 인자를 넘겼을 때 Print<int>().

실수형 인자를 넘겼을 때 Print<float>()등.. 이렇게 템플릿을 통해 생성된 함수를 템플릿의 인스턴스라 한다.

 

템플릿도 함수처럼 여러 템플릿 매개변수를 가질 수 있다.

template <typename T1, typename T2>
void Print(T1 a, T2 b)
{
	std::cout << a << b << std::endl;
}

int main(void)
{
	Print("Hello", "World!"); // 타입이 같아도 상관없다.
	Print(10, 50.0f);

	return 0;
}

이 또한 템플릿 인스턴스 함수를 생성하여 만들어진다.

 

함수 템플릿의 매개변수로 타입(자료형)뿐만 아니라 정수등도 가능하다.

template <typename T1,int size>
void Print(T1 a)
{
	for (int i = 0; i < size; ++i)
		std::cout << a[i] << std::endl;
}

int main(void)
{

	int arr[5] = { 1,2,3,4,5 };

	Print<int*,5>(arr);

	return 0;
}

 

다음으로는 템플릿 특수화라는게 있다. 그 전에 사용자 정의 자료형(Class,Struct)을 사용했을때 발생하기 쉬운 문제를 본다.

struct Test
{
	int m_testX = 100;
	int m_testY = 50;
};

template <typename T1>
void Print(T1 a)
{
	std::cout << a << std::endl;
}

위의 코드를 컴파일 해보면 컴파일 에러가 뜬다.

굳이 이걸 가능하게 하려면 << 연산자를 오버로딩 해야한다.

struct Test
{
	int m_testX = 100;
	int m_testY = 50;
	friend ostream& operator<<(ostream& os, const Test& test);
};

ostream& operator<<(ostream& os, const Test& test)
{
	cout << test.m_testX << " " << test.m_testY << endl;
	return os;
}

template <typename T1>
void Print(T1 a)
{
	std::cout << a << std::endl;
}

int main(void)
{
	Test t1;
	Print(t1);
	
	return 0;
}

 

이런 상황에서 템플릿 특수화가 사용될 수 있는 예인데 코드를 보자.

template <typename T1>
void Print(T1 a)
{
	std::cout << a << std::endl;
}
// 특수화 함수 템플릿
template< >
void Print(Test t1)
{
	cout << t1.m_testX << " " << t1.m_testY << endl;
}
int main(void)
{
	Test t1;
	Print(t1);
	
	return 0;
}

여기서 만약 참조형으로 넘겨주고 싶다면 명시적으로 형변환을 해야한다.

template <typename T1>
void Print(T1 a)
{
	std::cout << a << std::endl;
}
// 특수화 함수 템플릿
template< >
void Print(Test& t1)
{
	cout << t1.m_testX << " " << t1.m_testY << endl;
}

int main(void)
{
	Test t1;
	Print<Test&>(t1);
	
	return 0;
}

그 이유는 컴파일러가 이게 값형으로 넘기는건지 참조형으로 넘기는건지 확실하지 않으므로.

 

클래스 템플릿

클래스 템플릿도 함수 템플릿과 별반 다른게 없다. 단지 함수에서 클래스로 바뀐것뿐이다.(물론 struct도 가능하다)

 

함수 템플릿이 함수를 만들어내는 틀이라면 클래스 템플림은 클래스를 만들어 내는 틀이다.

 

class Array
{
public:
	Array(int capacity = 100) : m_size(0)
	{
		m_buffer = new int[capacity]();
	}
	virtual ~Array()
	{
		delete[] m_buffer;
	}
	void Push_Back(int data)
	{
		m_buffer[m_size++] = data;
	}
	int& operator[](int idx) const
	{
		return m_buffer[idx];
	}

	int GetSize() const
	{
		return m_size;
	}
private:
	int  m_capactiy;
	int  m_size;
	int* m_buffer;
};

위와 같이 배열의 기능을 사용하지만, 여러 인터페이스를 제공하는 클래스가 있다고 보자.

당연하겠지만 배열은 int형같은 정수형 타입뿐만 아니라 실수,문자등등을 저장해야한다.

이것을 무식하게 여러 타입이 필요할때마다 똑같은 기능을 하는데 타입만 다른 클래스를 여러번 만들수는 없다.

 

그렇기에 사용되는게 클래스 템플릿. 템플릿 매개변수 인자를 통해 호출자가 클래스에 사용될 타입을 결정한다.

template<typename T>
class Array
{
public:
	Array(int capacity = 100) : m_size(0),m_capacity(cpacity)
	{
		m_buffer = new T[capacity]();
	}
	virtual ~Array()
	{
		delete[] m_buffer;
	}
	void Push_Back(T data)
	{
		m_buffer[m_size++] = data;
	}
	T& operator[](int idx) const
	{
		return m_buffer[idx];
	}

	int GetSize() const
	{
		return m_size;
	}
private:
	int  m_capactiy;
	int  m_size;
	T* m_buffer;
};

int main(void)
{
	Array<int> iArr;
	iArr.Push_Back(10);
	iArr.Push_Back(20);
	iArr.Push_Back(30);
	
	return 0;
}

이렇게 클래스 템플릿 또한 함수 템플릿과 같이 컴파일러는 클래스 템플릿을 이용해 실제 클래스를 생성한다.

(클래스 인스턴스)

 

클래스 템플릿도 특수화가 가능하다.

template< >
class Array<std::string>
{
	string m_data;

public:
	Array()
	{
		std::cout << "string Template 특수화" << std::endl;
	}
	
};
int main(void)
{
	Array<int> iArr;
	iArr.Push_Back(10);
	iArr.Push_Back(20);
	iArr.Push_Back(30);
	std::cout << iArr[2];

	Array<string> strArr;

	return 0;
}

 

반응형
Comments