본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 13. 템플릿

따라하며 배우는 C++ 13. 템플릿

 

13.1 함수 템플릿

 

int getMax(int x, int y)
{
	return (x > y) ? x : y;
}

double getMax(double x, double y)
{
	return (x > y) ? x : y;
}
//이렇게 형이 여러 개일 경우 템플릿이 유용하다.

 

 

#include <iostream>
using namespace std;

template<typename T>
//typename 대신 class를 쓸 수도 있다.
//늬앙스가 약간 다를 뿐 거의 비슷한 기능을 한다.
T getMax(T x, T y)
{
	return (x > y) ? x : y;
}

int main()
{
	cout << getMax(1, 2) << endl;
	cout << getMax(1.241234, 3.24234) << endl;
	cout << getMax(1.2f, 3.2f) << endl;
	cout << getMax('a' , 'c') << endl;
	return 0;
}

 

컴파일러가 어떤 형이 들어갈지를 찾아내고, 실행될 때 내부적으로 함수 오버로딩처럼 작동되는 것.

T 안에 사용자 정의 클래스를 사용할 수도 있다.

 

템플릿으로 만든 function을 실제로 만드는 것을 instanciation이라고 부른다.

 

 

 

13.2 클래스 템플릿

 

 

MyArray.h

#pragma once
#include <assert.h>
#include <iostream>

template<typename T>
//T는 아무거나 사용할 수 있다. type에 대한 매개변수.
class MyArray
{
private:
	int m_length;
	T* m_data;
public:
	MyArray()
	{
		m_length = 0;
		m_data = nullptr;
	}

	MyArray(int length)
	{
		m_data = new T[length];
		m_length = length;
	}

	~MyArray()
	{
		reset();
	}

	void reset()
	{
		delete[] m_data;
		m_data = nullptr;
		m_length = 0;
	}

	T& operator[](int index)
	{
		assert(index >= 0 && index < m_length);
		return m_data[index];
	}

	int getLength()
	{
		return m_length;
	}

	void print()
	{
		for (int i = 0; i < m_length; ++i)
			std::cout << m_data[i] << " ";
		std::cout << std::endl;
	}
};

 

Source.cpp

 

#include <iostream>
#include "MyArray.h"
using namespace std;



int main()
{
	MyArray<double> my_array(10);
	for (int i = 0; i < my_array.getLength(); ++i)
		my_array[i] = i * 0.5;
	my_array.print();
	MyArray<double> my_array2(10);
	for (int i = 0; i < my_array2.getLength(); ++i)
		my_array2[i] = i + 65;
	my_array2.print();
	return 0;
}

 

 

 

 

#include "MyArray.h"

template<typename T>
void MyArray<T>::print()
{
	for (int i = 0; i < m_length; ++i)
		std::cout << m_data[i] << " ";
	std::cout << std::endl;
}
//explicit instantiation
//컴파일러가 인식을 할 수 없다면
template void MyArray<char>::print();
template void MyArray<double>::print();
template void MyArray<int>::print();
//로 빌드할 수 있다.

//클래스 자체를 explicit instantiation도 가능
//template class MyArray<char>
//template class MyArray<double>

 

main에서는 "MyArray.h"로 include하고, 

MyArray.h에 있는 함수의 body가 "MyArray.cpp"에 있는 경우 어떤 형으로 변환할지 컴파일러가 찾지 못할 수도 있다.

이럴 경우 cpp 아래에 저렇게 지정해주면 문제가 사라진다.

 

 

 

13.3 자료형이 아닌Non-type 템플릿 매개변수

 

#include <iostream>
#include "MyArray.h"
using namespace std;



int main()
{
	//정적 할당, 사이즈가 컴파일 타임에 알려져 있어야 함.
	MyArray<double, 100> my_array; //std::array<double, 100>
	for (int i = 0; i < my_array.getLength(); ++i)
		my_array[i] = i + 65;
	my_array.print();
	return 0;
}

 

MyArray.h

 

#pragma once
#include <assert.h>
#include <iostream>


//non-type template의 경우 형을 선언하기가 힘들기 때문에
//헤더에 무조건 집어넣는 편
//m_length를 대체하는 T_SIZE
template<typename T, unsigned int T_SIZE>
//T는 아무거나 사용할 수 있다. type에 대한 매개변수.
class MyArray
{
private:
	//int m_length;
	T* m_data; // T m_data[m_length]
public:
	MyArray()
	{
		m_data = new T[T_SIZE];
	}

	~MyArray()
	{
		delete[] m_data;
	}

	T& operator[](int index)
	{
		assert(index >= 0 && index < T_SIZE);
		return m_data[index];
	}

	int getLength()
	{
		return T_SIZE;
	}

	void print()
	{
		for (int i = 0; i < T_SIZE; ++i)
			std::cout << m_data[i] << " ";
		std::cout << std::endl;
	}
};

 

 

 

 

 

 

13.4 함수 템플릿 특수화

 

template<typename T>

T getMax(T x, T y)

처럼 선언되어 있는 경우,

 

getMax(1, 2);

getMax<double>(1.0, 2.0);

처럼도 사용 가능하다.

 

#include <iostream>
using namespace std;

template <typename T>
T getMax(T x, T y)
{
	return (x > y) ? x : y;
}
template<>
char getMax(char x, char y)
{
	cout << "Warning : comparing chars" << endl;
	return (x > y) ? x : y;
}
int main()
{
	cout << getMax('a', 'b') << endl;
	return 0;
}

 

또한 template이 사용되고 있는 함수에 대해, 특정 변수형에 대해선 특수하게 작동되도록 하고 싶을 때,

역으로 그 변수형에 대해 함수 템플릿을 특수화시킬 수 있다.

 

 

Source.cpp

#include <iostream>
#include "Storage.h"

int main()
{
	Storage<int> nValue(5);
	Storage <double> dValue(6.1);

	nValue.print();
	dValue.print();
	return 0;
}

Storage.h

 

#pragma once
#include <iostream>

template <class T>
class Storage
{
private: 
	T m_value;
public:
	Storage(T value)
	{
		m_value = value;
	}

	~Storage()
	{}

	void print()
	{
		std::cout << m_value << '\n';
	}
};

template<>
void Storage<double>::print()
{
	std::cout << "Double Type : ";
	std::cout << std::scientific << m_value << '\n';
}

 

 

이 때 cpp 파일에 특수화시킨 걸 집어넣어 정리하는 경우

template<>
void Storage<double>::print()
{
	std::cout << "Double Type : ";
	std::cout << std::scientific << m_value << '\n';
}

그냥 헤더에 넣는 것이 일반적이지만,

header에도 cpp 파일을 include하는 식으로 해결할 수 있다.

 

 

13.5 클래스 템플릿 특수화

 

 

#include <iostream>
#include <array>
#include "Storage.h"
using namespace std;

template<typename T>
class A
{
public:
	void doSomething()
	{
		cout << typeid(T).name() << endl;
	}
	void test() {}
};

template<>
class A<char>
{
public:
	void doSomething()
	{
		cout << "Char type specialization" << endl;
	}
};

int main()
{
	A<int> a_int;
	A<double> a_double;
	A<char> a_char;
	a_int.doSomething();
	a_double.doSomething();
	a_char.doSomething();
	return 0;
}

//실제로는 다른 클래스를 정의하는 것과 진배 없기는 하다.

 

 

int main()
{
	A<int> a_int;
	A<double> a_double;
	A<char> a_char;
	a_int.doSomething();
	a_double.doSomething();
	a_char.doSomething();

	a_int.test();
	a_double.test();
	//a_char.test(); //불가
	return 0;
}

문법 상에는 아예 다른 클래스 취급된다.

단, instanciation처럼 사용할 수 있어 편리해진다.

 

Source.cpp

#include <iostream>
#include <array>
#include "Storage8.h"
using namespace std;

template<typename T>
class A
{
public:
	void doSomething()
	{
		cout << typeid(T).name() << endl;
	}
	void test() {}
};

template<>
class A<char>
{
public:
	void doSomething()
	{
		cout << "Char type specialization" << endl;
	}
};

int main()
{
	Storage8<int> intStorage;
	for (int count = 0; count < 8; ++count)
		intStorage.set(count, count);

	for (int count = 0; count < 8; ++count)
		std::cout << intStorage.get(count) << std::endl;

	cout << "Size of Storage8<int> " << sizeof(Storage8<int>) << std::endl;

	Storage8<bool> boolStorage;
	for (int count = 0; count < 8; ++count)
		boolStorage.set(count, count);

	for (int count = 0; count < 8; ++count)
		std::cout << boolStorage.get(count) << std::endl;

	cout << "Size of Storage8<bool> " << sizeof(Storage8<bool>) << std::endl;
	return 0;
}

 

Storage8.h

 

#pragma once
template <class T>
class Storage8
{
private:
	T m_array[8];
public:
	void set(int index, const T& value)
	{
		m_array[index] = value;
	}

	const T& get(int index)
	{
		return m_array[index];
	}
};

template<>
class Storage8<bool>
{
private:
	unsigned char m_data;
public:

	Storage8() : m_data(0)
	{}

	void set(int index, bool value)
	{
		unsigned char mask = 1 << index;
		if (value)
			m_data |= mask;
		else m_data &= ~mask;
	}

	bool get(int index)
	{
		unsigned char mask = 1 << index;
		return (m_data & mask) != 0;
	}
};

 

13.6 템플릿을 부분적으로 특수화하기

 

 

#include <iostream>
#include <array>
#include "Storage8.h"
using namespace std;

template <class T, int size>
class StaticArray_BASE
{
private:
	T m_array[size];
public:
	T* getArray() { return m_array; }
	T& operator[](int index)
	{
		return m_array[index];
	}

	void print()
	{
		for (int count = 0; count < size; ++count)
			cout << (*this)[count] << ' ';
		cout << endl;
	}
};

template<class T, int size>
class StaticArray : public StaticArray_BASE<T, size>
{	
};

template<int size>
class StaticArray<char, size> : public StaticArray_BASE<char, size>
{
public:
	void print()
	{
		for (int count = 0; count < size; ++count)
			cout << (*this)[count];
			cout << endl;
	}
};
int main()
{
	StaticArray<int, 4> int4;
	int4[0] = 1;
	int4[1] = 2;
	int4[2] = 3;
	int4[3] = 4;
	int4.print();

	StaticArray<char, 14> char14;
	strcpy_s(char14.getArray(), 14, "Hello World!");
	char14.print();
	return 0;
}

 

 

함수 타입에 대해 상속을 이용해 specialization 실행.

 

 

 

13.7 포인터에 대한 템플릿 특수화

 

#include <iostream>
#include <array>
using namespace std;

template<class T>
class A
{
private:
	T m_value;
public:
	A(const T& input)
		: m_value(input)
	{

	}

	void print()
	{
		cout << m_value << endl;
	}
};
int main()
{
	A<int> a_int(123);
	a_int.print();

	int temp = 456;
	A<int*> a_int_ptr(&temp);
	a_int_ptr.print();
	double temp_d = 3.141592;
	A<double*> a_double_ptr(&temp_d);
	a_double_ptr.print();
	return 0;
}

 

 

포인터에 대한 값은 주소가 나온다.

 

#include <iostream>
#include <array>
using namespace std;

template<class T>
class A
{
private:
	T m_value;
public:
	A(const T& input)
		: m_value(input)
	{

	}

	void print()
	{
		cout << m_value << endl;
	}
};

template<class T>
class A<T*>
{
private:
	T* m_value;
public:
	A (T* input)
		: m_value(input)
	{

	}

	void print()
	{
		cout << *m_value << endl;
	}
};

int main()
{
	A<int> a_int(123);
	a_int.print();

	int temp = 456;
	A<int*> a_int_ptr(&temp);
	a_int_ptr.print();
	double temp_d = 3.141592;
	A<double*> a_double_ptr(&temp_d);
	a_double_ptr.print();
	return 0;
}

 

 

 

 

13.8 멤버 함수를 한 번 더 템플릿화 하기 Templatize

 

#include <iostream>
#include <array>
using namespace std;

template<class T>
class A
{
private:
	T m_value;
public:
	A(const T& input)
		: m_value(input)
	{

	}

	template<typename TT>
	void doSomething(const TT& input)
	{
		cout << typeid(T).name() << " " << typeid(TT).name() << endl;
		cout << (TT)m_value << endl;
	}

	void print()
	{
		cout << m_value << endl;
	}
};

int main()
{
	A<int> a(123);
	a.print();
	a.doSomething(123.4);
	a.doSomething<float>(24.3);
	return 0;
}

	void doSomething()
	{
		cout << typeid(T).name() << " " << typeid(TT).name() << endl;
		cout << (TT)m_value << endl;
	}

	void print()
	{
		cout << m_value << endl;
	}
};

int main()
{
	A<char> a_char('A');
	a_char.print();
	a_char.doSomething<int>();
	return 0;
}

	template<typename TT>
	void doSomething(const TT& input)
	{
		cout << typeid(T).name() << " " << typeid(TT).name() << endl;
		cout << (TT)m_value << endl;
	}

	void print()
	{
		cout << m_value << endl;
	}
};

int main()
{
	A<char> a_char('A');
	a_char.print();
	a_char.doSomething(int());
	return 0;
}

 

 

위의 경우도 가능!!