[기술 질문]#14_템플릿, Template

2023. 3. 12. 09:50· 언어/기술 질문
목차
  1.  
  2. [기술 질문] #14_템플릿, Template

 

[기술 질문] #14_템플릿, Template

 

C++의 템플릿에 대해 알아보겠습니다.

 


 

Overview

 

  1. 개념
  2. 템플릿 변수
  3. 템플릿 함수
  4. 템플릿 non-type 파라미터
  5. 템플릿 클래스
  6. 이중 템플릿

 

#0. 개념

1. 정의

  • C++의 '템플릿'은 함수나 클래스가 특정한 데이터 형식이 유형에 귀속되지 않고, 일반화되어 여러 데이터 형에 대해 동작할 수 있도록 하는 '제네릭 프로그래밍'을 제공합니다. 따라서, 템플릿은 서로 다른 유형의 클래스 혹은 함수의 인스턴스 생성을 위한 청사진을 제공하며, 컴파일 시점에 그 상세 유형이 결정됩니다.

 

2. 특징 

  1. [ 일반화 ] : 템플릿은 특정한 데이터 형식에 의존하지 않고 여러 종류의 데이터 형식에 대해 동작할 수 있도록 합니다. 이러한 특징은 사용자로 하여금 코드 작성의 유연성과 재사용성을 높여줍니다.
  2. [ 템플릿 인스턴스화 ] : 템플릿은 컴파일 시점에 그 상세 유형이 결정됩니다. 이때, 컴파일러는 해당 데이터 형식에 맞는 코드를 생성하여 사용합니다.
  3. [ 디버깅 ] : 템플릿 사용 시 특정 데이터 유형에 특수화된 코드가 생성되므로, 에러 발생 시에 디버깅이 다소 복잡합니다.
  4. [ 장점 ] : 템플릿 활용은 코드 작성의 유연성과 유지보수성 향상의 장점이 있습니다.

 

3. 일반화 프로그래밍(Generic Programming)

  • [ 정의 ] : 일반화 프로그래밍은 데이터 형식으로부터 독립적이며, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 일반적이며, 재사용성 높은 코드 작성을 강조하는 프로그래밍 패러다임입니다. 일반화 프로그래밍의 목표는 코드의 재사용성과 유지 보수성 향상입니다. 

 

4. 기본 문법

template<typename T>
or
template<class T>

 

Details

 

  1. 템플릿은 데이터 타입을 파라미터로 전달 받습니다.
  2. typename 키워드는 어떤 이름이 변수 혹은 함수가 아니라, 템플릿 내 의존 이름이 데이터 유형임을 가리킵니다.
  3. 주의할 점은 템플릿 클래스 혹은 함수를 실제 사용할 특정 데이터 타입으로 인스턴스화한 결과물로 지칭해야 합니다.

 

5. 컴파일러의 템플릿 코드 처리

  • 컴파일러가 템플릿 정의 코드를 만나면 문법 검사만 수행하고, 실제 컴파일은 수행하지 않습니다.
  • 컴파일러는 실제 사용할 특정 데이터 타입으로 정의한(eg. myClass <int>) 템플릿 인스턴스화 코드를 만나면 새로운 정의 코드를 생성합니다. 따라서, 아직 인스턴스화되지 않은 템플릿 코드는 컴파일되지 않습니다.

 

#1. 템플릿 변수

1. 기본 문법

template<typename T>
constexpr T pi = T(3.14...);

int main()
{

    float piToFloat = pi<float>;
    double piToDouble = pi<double>;

}

 

#2. 템플릿 함수

1. 호출

  1. [ 타입 연역(Type Deduction) ] : 하나는 "타입 연역(Type Deduction)"을 통해 기존의 호출방법을 사용하는 방법입니다. SomeFunc() 방식으로 동작합니다.
  2. [ 명시적 타입 지정 ] : 다른 하나는 명시적으로 타입을 지정해 템플릿 함수를 호출하는 방법입니다. SomeFunc<int>() 처럼, 명시적으로 타입을 지정해 템플릿 함수를 호출하는 방법입니다.

 

2. 예제 1

#include <iostream>
#include <vector>

static const size_t NOT_FOUND = (size_t)(-1);

template<typename T>
size_t Find(T& val, T* arr, size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		if (arr[i] == val)
			return i;
	}
	return NOT_FOUND;
}

int main()
{
    size_t arrSize = 5;
    int a[arrSize] = { 1, 2, 3, 4, 5 };
    int b = 2;
    
    size_t Idx1 = Find<int>( b, a, arrSize );  
    size_t Idx2 = Find( b, a, arrSize );
    
    cout << Idx1 << endl;
    cout << Idx2 << endl;
    
    return 0;
}

 

3. 예제 2

#include <iostream>

// Template function to find the maximum of two values
template <typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    int x = 5, y = 10;
    std::cout << "max(" << x << ", " << y << ") = " << max(x, y) << std::endl;

    double a = 3.14, b = 2.71;
    std::cout << "max(" << a << ", " << b << ") = " << max(a, b) << std::endl;

    return 0;
}

 

4. 특수화

// 원본 템플릿 함수
template<typename T>
size_t Find(T& val, T* arr, size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		if (arr[i] == val)
			return i;
	}
	return NOT_FOUND;
}

// string에 대한 특수화
template<>
size_t Find<string>(string& val, string* arr, size_t size)
{
	for (size_t i = 0; i < size; i++)
	{
		if (arr[i] == val)
			return i;
	}
	return NOT_FOUND;
}

 

Details

 

  • 일반 템플릿 함수는 특정 데이터 타입에 대한 특수화 작업을 수행할 수 있습니다.

 

5. 템플릿 함수 오버로딩

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

static constexpr size_t NOT_FOUND = (size_t)(-1);


// 2. Find<T>() 메서드를 오버로딩한 함수
void Find(string& val, vector<string> arr, size_t size)
{
	cout << "오버로드 함수" << endl;
}

// 1. 원본 템플릿 함수
template<typename T>
void Find(T& val, vector<T> arr, size_t size)
{
	cout << "템플릿 함수" << endl;
}

int main()
{

	vector<string> v = { "One", "Two"};
	size_t arrSize = v.size();
	string findString = "Two";

	Find(findString, v, arrSize);

	return 0;

}

 

Details

 

  • 템플릿 함수 또한 일반 함수처럼 오버로딩이 가능합니다.
  • 주의할 점은 "타입 연역", 즉 <>를 활용해 특정 데이터 유형을 명시적으로 작성해 템플릿 함수를 호출하지 않을 경우 오버로딩한 함수가 템플릿 함수의 이름을 가릴 수 있습니다.

 

#3. 템플릿 non-type 파라미터

1. 개념

  • [ 정의 ] : 템플릿 코드를 작성하기 위해 우리는 <> 안에 파라미터 목록들을 작성합니다. 템플릿 코드의 파라미터는 원하는 개수만큼 작성이 가능하며, 꼭 데이터 타입이 아니어도 됩니다.
  • [ 특징 ] : (1) 메모리 할당 시점 : non-type 파라미터를 받는 템플릿 코드는 컴파일 시점에 파라미터 값에 대한 메모리 할당이 이루어져, 코드 최적화가 가능합니다. (2) 특정 값에 대한 최적화 : non-type 파라미터를 받는 템플릿 코드 작성은 기존의 템플릿 코드의 일반화 특성을 유지하며, 특정 값에 대한 최적화가 컴파일 시점에 이루어져 성능 향상을 기대할 수 있습니다. 왜냐하면, 런 타임에 메모리 할당과 이루어지는 일반 함수의 파라미터 혹은 지역 변수들과 달리, 템플릿 함수의 non-type 파라미터는 컴파일 시점에 상수 값으로 결정되어 메모리 할당이 이루어집니다. 따라서, 런 타임에 발생하는 메모리 할당 작업이 컴파일 시점으로 앞당겨져 런 타임 오버헤드를 방지합니다.
  • [ 주의할 점 ] : 서로 다른 non-type 파라미터로 인스턴스화된 서로 다른 두 객체는 동일한 클래스 유형임에도 불구하고, 서로 다른 타입으로 취급됩니다. 예를 들면, MyClass<int, 10, 10>Class 1 과 MyClass<int, 11, 11>Class2 는 동일한 클래스 유형이지만, "10, 10"과 "11, 11" 처럼 서로 다른 non-type 파라미터를 받아 인스턴스화되었기 때문에, 다른 유형으로 취급됩니다. 따라서, 서로 간 복사 생성자 혹은 복사 대입 연산은 불가능합니다.

 

2. non-type 파라미터

template<typename T, size_t WIDTH, size_t HEIGHT>
class MyClass
{
public:
	MyClass();
	virtual ~MyClass();

	void setElementAt(size_t x, size_t y, const T& inElement);
	const T& getElementAt(size_t x, size_t y) const;

	// 템플릿 파라미터 목록 중 HEIGHT와 WIDTH를 반환하는 메서드
	size_t getHeight() const { return HEIGHT; }
	size_t getWidth() const { return WIDTH; }

private:
	T cells[WIDTH][HEIGHT];
};

 

Details

 

  • 템플릿 코드의 <> 안에 작성된 "WIDTH"와 "HEIGHT"는 데이터 유형이 아니지만, 코드는 정상적으로 작동됩니다.
  • 이처럼, 클래스의 생성자 혹은 메서드에서 데이터 값을 결정하지 않고, 템플릿 코드의 파라미터로 전달받아 그 크기를 컴파일 타임에 결정할 수 있기 때문에, 최적화의 이점을 갖고 있습니다.
  • 주의할 점은 파라미터로 전달받은 서로 다른 "데이터 타입"을 통해 템플릿으로 작성된 원본 클래스 혹은 함수들이 서로 다른 유형으로 취급받는 것처럼, 전달받은 서로 다른 비 데이터 타입의 파라미터들 또한 다른 타입으로 취급됩니다. 예를 들면, MyClass <int, 10, 10>과 MyClass<int, 11, 11>은 서로 다른 타입이 됩니다 

 

3. non-type 파라미터의 디폴트 값

template< typename T = int, size_t WIDTH = 10, size_t HEIGHT = 10 >
class MyClass
{
    //...
};

MyClass <> myClass;    // 문제 없이 컴파일됩니다!
MyClass<int> myClass2;    // 역시, 문제 없습니다!
MyClass<int, 5> myClass3;    // 이것 또한 문제없습니다!

 

Details

 

  • 물론, non-type 파라미터들도 일반 함수와 같이 디폴트 값을 지정할 수 있습니다.

 

 

#4. 템플릿 클래스

1. 템플릿 클래스

template<typename T>
class myClass
{
    //...
}

 

Details

 

  • [개념] : C++의 템플릿을 활용해 여러 데이터 타입에서 작동할 수 있는 일반환된 템플릿 클래스를 정의할 수 있습니다.

 

2. 선언부와 정의부 파일 나누기

// #!. 하나의 헤더파일에 템플릿 클래스의 선언부와 정의부를 모두 작성하는 방법

#pragma once

template<class T>
class TestClass
{
public:
	void SetVal(T _val);
    T GetVal();
};

template <typename T>
void TestClass<T>::SetVal(T _val)
{
//...
}

//...

 

Details

 

  • [개념] : 헤더 파일 1 : 클래스 정의와 메서드 정의를 하나의 헤더 파일에 모두 하는 방법

 

// #2. 헤더파일1(.h)에 클래스의 선언부 작성, 헤더파일2(.hpp/.tpp)에 클래스의 정의부 작성

// TestClass.h 파일
#pragma once

template <class T>
class TestClass
{
private:
	T val;

public:
	void SetVal(T _val);
	T GetVal();
};

#include "TestClass.hpp"

// TestClass.hpp 파일


template<typename T>
void TestClass<T>::SetVal(T _val)
{
	val = _val;
}

template<typename T>
T TestClass<T>::GetVal()
{
	return val;
}

 

Details

 

  • [개념] : 헤더 파일1(.h) + 헤더 파일2(.hpp) : 클래스 정의를 하나의 헤더 파일에, 그리고 메서드 정의를 다른 하나의 헤더 파일에 정의하고 클래스 정의부에서 해당 헤더 파일을 #include 합니다.
  • [주의할 점] : (1)파일 나누기 : 헤더파일2는 ".tpp" 혹은 ".hpp"로 생성한는 것이 일반적입니다. ".tpp"의 경우 인텔리센스가 작동하지 않아 불편하기 때문에, 헤더파일 형식으로 생성하고 확장명을 ".hpp"로 변경해 활용하는 것이 편합니다. (2) #include : 헤더파일2는 헤더파일1을 #include 하지 않습니다. 따라서, 함수를 정의할 때 "TestClass" 같이 클래스 명이 인식되지 않는 오류가 나오는데, 컴파일하면 정상적으로 동작합니다.

 

3. 템플릿 클래스 메서드 정의

// #1. 헤더파일1에 선언부와 정의부 모두 작성할 경우
#pragma once
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
class myClass
{
public:
	myClass(T a, T b);

public:
	T GetA();
	T GetB();

private:
	T myValA;
	T myValB;
};

template<typename T>
inline myClass<T>::myClass(T a, T b)
{
}

template<typename T>
inline T myClass<T>::GetA()
{
	return myValA;
}

template<typename T>
inline T myClass<T>::GetB()
{
	return myValB;
}
// #2. 헤더파일1(.h)과 헤더파일2(.hpp)로 나눌 경우

// myClass.h 파일 내부 
#pragma once
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
class myClass
{
public:
	myClass(T a, T b);

public:
	T GetA();
	T GetB();

private:
	T myValA;
	T myValB;
};

#include "myClass.hpp"

// myClass.hpp 파일 내부

template<typename T>
myClass<T>::myClass(T a, T b)
{
	myValA = a;
    myValB = b;
}

template<typename T>
T myClass<T>::GetA()
{
	return myValA;
}

template<typename T>
T myClass<T>::GetB()
{
	return myValB;
}

 

 

Details

 

  • [개념] : 템플릿 클래스의 메서드 정의부는 위에서 설명한 "파일 나누기" 항목을 살펴보면 됩니다. 템플릿 클래스의 메서드를 정의할 때 범위 지정 연산자를 통해 "myClass <T>"처럼, 특정 데이터 타입으로 인스턴스화된 결과물로 지정해야 합니다.

 

4. 템플릿 인스턴스화 대상 타입 제한

// 헤더파일(.h)
#include <iostream>
#include <vector>
using namespace std;

template<typename T>
class myClass
{
public:
	myClass(T a, T b);

public:
	T GetA();
	T GetB();

private:
	T myValA;
	T myValB;
};

// 헤더파일2(.hpp)

template<typename T>
myClass<T>::myClass(T a, T b)
{
    myValA = a;
    myValB = b;
}

template<typename T>
T myClass<T>::GetA()
{
	return myValA;
}

template<typename T>
T myClass<T>::GetB()
{
	return myValB;
}

template class myClass<int>;
template class myClass<double>;
template class myClass<vector<int>>;

// 메인 문
#include "myClass.h"

int main()
{

	myClass<int> classA(1, 2); // 오케이!!
	myClass<string> classB("One", "Two");	// 에러!!

	return 0;
}

 

Details

 

  • [개념] : 정의한 템플릿 클래스에 대해 인스턴스화 대상 타입들을 제한할 수 있습니다. 인스턴스화 대상 타입을 제한하기 위해 템플릿 클래스의 메서드를 정의한 소스파일(.hpp)의 마지막에 "template class myClass <대상타입>"을 작성합니다. 헤더파일1(.h)에 인스턴스화 대상 타입 제한 코드를 작성해도 무방합니다.

 

5. 특수화

// 헤더 파일
template<typename T>
class myClass
{
   //...
}

template<>
class myClass<string>
{
    //...
}

 

Details

 

  • [개념] : 템플릿 클래스 특수화는 기존의 템플릿 코드가 특정 데이터 타입에 대해 일반적으로 동작하지 않는 경우, 그 데이터 타입에 대한 별도의 코드를 작성하여 해당 데이터 타입에 대해 템플릿 클래스가 특수한 동작을 하도록 합니다.
  • [특징] : 템플릿 클래스 특수화는 일반화된 속성들 혹은 기능들을 유지하며 특정 데이터 타입들에 대해 더욱 최적화된 혹은 특수한 행동들을 구현합니다.
  • [장점&단점] : (1) 장점: 템플릿 클래스 특수화의 장점은 사용자 입장에서 해당 버전의 특수화 클래스가 가려져 캡슐화의 장점이 존재합니다. 더불어, 특정 데이터 타입에 대한 최적화가 가능해 성능 향상도 기대할 수 있습니다. (2) 단점 : 템플릿 클래스 특수화 버전은 기존의 일반화 버전보다 선택적 우위를 가지며, 컴파일러에게 혼란을 줄 수 있습니다. 더불어, 클래스 전체를 재작성해야 하는 불편함과 코드의 유지보수성을 해칠 수 있습니다.
  • [주의할 점] : 주의할 점은 템플릿 특수화는 어떠한 코드도 상속받지 않기 때문에, 클래스 전체를 재작성해야 합니다.

 

6. 상속

// 슈퍼 클래스(.h)
template<typename T>
class SuperClass
{
    //...
}

// 서브 클래스(.h)
template<typename T>
class SubClass : public SuperClass<T>
{
public:
    SubClass( size_t inWidth = SuperClass<T>::DefaultWidth, size_t Height = SuperClass<T>::DefaultHeight)
    void Move( size_t srcX, size_t srcY, size_t destX, size_t destY ); 
};

// 서브 클래스 메서드의 정의부(.cpp)
template<typename T>
SubClass<T>::SubClass(size_t inWidth, size_t inHeight )
{
    //...
}

template<typename T>
void SubClass<T>::Move(size_t srcX, size_t srcY, size_t destX, size_t destY)
{
    //...
}

 

Details

 

  • [개념] : 템플릿 클래스 또한 일반 클래스처럼 상속 기능을 제공합니다.
  • [특징] : 이때, 템플릿 부모 클래스를 상속하는 자식 클래스는 부모 클래스와 같이 "템플릿" 형식으로 작성해야 합니다.

 

7. 특수화 vs 상속

  1. 코드 재사용 : 상속의 경우 코드 재작성이 필요가 없습니다. 하지만, 특수화의 경우 코드를 모두 재작성해야 합니다.
  2. 이름 재사용 : 상속의 경우 부모 클래스의 이름과 자식 클래스의 이름이 다릅니다. 하지만, 특수화의 경우 부모 클래스의 것과 같습니다.
  3. 다형성 지원 : 상속의 경우 "is-a" 관계가 성립됩니다. 하지만, 특수화의 경우 같은 클래스라도 서로 다른 타입으로 인스턴스화된 클래스 객체들이 되며 서로 다른 타입으로 취급됩니다.
  4. 결론 : 다형성의 장점을 활용하기 위해 우리는 템플릿 상속을 사용하며, 특정 데이터 타입에 대한 특수화를 통해 일반화 프로그래밍의 장점과 더불어 캡슐화의 장점을 취하기 위해 템플릿 클래스 특수화를 활용해 볼 수 있겠습니다.

 

#5. 이중 템플릿

1. 이중 템플릿

  • [개념] : C++는 템플릿 클래스의 개별 메서드에 대한 이중 템플릿화를 지원합니다.
  • [특징] : 동일한 클래스 유형과 다른 데이터 타입으로 인스턴스화된 서로 다른 두 객체는 복제 생성자 혹은 복제 대입 연산자의 활용이 불가능합니다. 따라서, 우리는 이중 템플릿을 활용해 템플릿 클래스 내 복제 생성자 혹은 대입 연산자를 템플릿 코드로 재정의하여 같은 클래스로부터 생성된 서로 다른 유형의 객체들 간의 대입 연산 혹은 복제 생성을 수행할 수 있게 됩니다!!
  • [주의할 점] : (1) 가상 멤버 함수와 소멸자 : 가상 멤버 함수와 소멸자는 템플릿 코드로 작성할 수 없습니다. (2) 문법 : template<typename T, typename E>로 작성하면 안 됩니다!!!

 

2. 예제

// MyClass.h 파일 내부 

template<typename T, size_t WIDTH, size_t HEIGHT>
class MyClass
{
public:
	MyClass();
	virtual ~MyClass();

	// 복제 생성자의 템플릿화
	template<typename E>
	MyClass(const MyClass<E>& src);

	// 대입 연산자의 템플릿화
	template<typename E>
	MyClass<T>& operator=(const MyClass<E>& rhs);

	void setElementAt(size_t x, size_t y, const T& inElement);
	const T& getElementAt(size_t x, size_t y) const;

	size_t getHeight() const { return HEIGHT; }
	size_t getWidth() const { return WIDTH; }

private:
	T cells[WIDTH][HEIGHT];
};

#include "MyClass.tpp"

// MyClass.tpp 파일 내부

// template<typename T, typename E> -> XXXX 안됩니다!!
template<typename T>
template<typename E>
MyClass<T>& operator=(const MyClass<E> rhs)
{
    //...
}

 

Details

 

  • 같은 템플릿 클래스로부터 생성된 서로 다른 타입의 객체들 간의 복제 혹은 대입 연산이 가능해졌습니다!
  • 정리하자면, 템플릿 클래스는 전체적으로 typename T에 대해 템플릿화 되어있으며, 개별적으로 사용자 정의 복제 생성자와 대입 연산자는 template<typename E>로 템플릿화 하여 같은 템플릿 클래스로부터 서로 다른 데이터 타입으로 인스턴스화된 객체들 간의 복제 혹은 대입 연산이 가능합니다.

 

 

 

'언어 > 기술 질문' 카테고리의 다른 글

[기술 질문]#16_RTTI, 런타임 타입 정보  (0) 2023.03.22
[기술 질문]#15_가상 함수  (0) 2023.03.20
[기술 질문]#13_6가지 디폴트 멤버 메서드  (0) 2023.03.05
[기술 질문]#12_함수 포인터, Function Pointer  (0) 2023.02.22
[기술 질문]#11_허상 포인터(Dangling Pointer)  (2) 2023.02.18
  1.  
  2. [기술 질문] #14_템플릿, Template
'언어/기술 질문' 카테고리의 다른 글
  • [기술 질문]#16_RTTI, 런타임 타입 정보
  • [기술 질문]#15_가상 함수
  • [기술 질문]#13_6가지 디폴트 멤버 메서드
  • [기술 질문]#12_함수 포인터, Function Pointer
Hardii2
Hardii2
Hardii2
개발 블로그
Hardii2
전체
오늘
어제
  • 분류 전체보기
    • 알고리즘
    • 웹 개발
      • Node.js
      • React
    • 게임개발
      • DirectX12
      • 관련 지식
      • Unreal C++
      • Unreal 블루프린트
    • 언어
      • Effective C++
      • Basic C++
      • 디자인 패턴
      • 자료구조
      • 기술 질문
    • 문제 풀이
      • BOJ 문제 풀이
      • Programmers 문제 풀이
      • geeksForgeeks 문제 풀이
    • 수학
      • 확률과 통계
      • 게임수학
    • 개인프로젝트
    • 그룹프로젝트
      • PM
      • Dev
    • Github

블로그 메뉴

  • 홈
  • 글쓰기

공지사항

인기 글

태그

  • Effective C++
  • 개발
  • stl
  • BFS
  • 디자인 패턴
  • set
  • 우선순위 큐
  • programmers
  • DP
  • 그래프
  • unreal
  • C++
  • Unreal Blueprint
  • 정렬
  • 트리
  • BOJ
  • 최단 경로
  • 기술 질문
  • dfs
  • 알고리즘

최근 댓글

최근 글

hELLO · Designed By 정상우.v4.2.2
Hardii2
[기술 질문]#14_템플릿, Template
상단으로

티스토리툴바

개인정보

  • 티스토리 홈
  • 포럼
  • 로그인

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.