[C++기초] 11. 템플릿 프로그래밍
INDEX
함수 템플릿
템플릿이란?
-
Java와 C#에서의 제네릭 메서드/ 클래스와 비슷
-
STL컨테이너 또한 템플릿
-
덕분에 코드를 자료형마다 중복으로 작성하지 않아도 됨
-
작동 원리 : 템플릿을 인스턴스할 때마다 컴파일러가 내부적으로 코드를 생성
-
- 템플릿에 넣는 자료형 가지수에 비례해서 exe파일 크기가 증가한다
- 컴파일 타임에 어느 정도 다형성을 부여할 수 있음
템플릿 함수 예시
template<typename T> //또는 template<class T> T Add(T a, T b) { return a + b; } int main() { std::cout<<Add<int>(3,10)<<std::endl; std::cout<<Add<float>(3.14f,10.14f)<<std::endl; return 0; }
함수 템플릿
-
함수 템플릿을 호출할 때 템플릿 매개변수를 생략할 수 있음
-
Add<int>(3,10);
-
Add(3,10); //생략가능
template<class <type-name>> <function_declaration>; template<typename <type-name>> <function_declaration>;
template<class T> T Add(T a, T b) { //... }
template<typename T> T Add(T a, T b) { //... }
typename vs class
- 사실상 차이는 없음
- 그냥 typename을 사용하자
클래스 템플릿
클래스도 템플릿으로 만들 수 있을까?
-
Yes!
-
클래스 탬플릿에서 개채를 선언할 때 템플릿 매개변수를 명시해야 함!!
-
MyArray<int> scores; // OK
MyArray scores; // Error
- 함수템플릿과 달리 클래스 템플릿은 컴파일러가 알아서 하기 힘들기 때문
-
예시 (Error 존재)
// MyArray.h #pragma once template<typename T> class MyArray { public: bool Add(T data); MyArray(); private: enum { MAX = 3 }; int mSize; T mArray[MAX]; }
// MyArray.cpp #include"MyArray.h" template<typename T> bol MyArray<T>::Add(T data) { if (mSize >= MAX) { return false; } mArray[mSize++] = data; return true; } template<typename T> MyArray<T>::MyArray() : mSize(0) { }
// Main.cpp #include "MyArray.h" int main() { MyArray<int> scores; scores.Add(10); // true 반환 scores.Add(20); // true 반환 scores.Add(30); // true 반환 scores.Add(40); // false 반환 return 0; }
-
위의 예제는 에러 발생
-
-
컴파일러가 “Main.cpp”를 컴파일할 때, “MyArray.cpp”를 못찾음
-
- “MyArray.h”를 통해서 오직 MyArray클래스 선언만 볼 수 있음
- 따라서, 컴파일러가 MyArray
를 만들어줄 수 없음
-
- 해결책 : 헤더 파일안에 구현체를 다 넣어야 한다..
-
-
예시 (Error 없음)
//MyArray.h #pragma once template<typename T> class MyArray { public: bool Add(T data); MyArray(); private: enum { MAX = 3 }; int mSize; T mArray[MAX]; } template<typename T> bol MyArray<T>::Add(T data) { if (mSize >= MAX) { return false; } mArray[mSize++] = data; return true; } template<typename T> MyArray<T>::MyArray() : mSize(0) { }
//Main.cpp #include "MyArray.h" int main() { MyArray<int> scores; scores.Add(10); // true 반환 scores.Add(20); // true 반환 scores.Add(30); // true 반환 scores.Add(40); // false 반환 return 0; }
클래스 템플릿 트릭
벡터를 사용하고 싶은데, 벡터가 늘어나지 않았으면 좋겠다!
//FixedVector.h template<typeName T, size_t N> class FixedVector { public: //public 메서드들 private: T mArray[N]; };
//main.cpp FixedVector<int, 16> numbers;
더 자세한 예시
//FixedVector.h #pragma once namespace samples { //N은 size_t형이어야 함 template<typename T, size_t N> class FixedVector { public: FixedVector(); bool Add(const T& data); size_t GetSize() const; size_t GetCapacity() const; //enum 트릭이 없다! private: size_t mSize; // mArray를 정적으로 만듦 (new를 안 쓰는 것은 언제나 좋다) T mArray[N]; }; // 구현부 ~ // 생성자 template<typename T, size_t N> FixedVector<T, N>::FixedVector() : mSize(0) { } template<typename T, size_t N> size_t FixedVector<T, N>::GetSize() const { return mSize; } template<typename T, size_t N> size_t FixedVector<T, N>::GetCapacity() const { return N; } template<typename T, size_t N> bool FixedVector<T, N>::Add(const T& data) { if(mSize>= N) { //여기에 assert 넣어도 됨. return false; } mArray[mSize++] = data; return true; } // ~ 구현부 }
//main.cpp #include<iostream> #include"FixedVector.h" #include"FixedVectorExample.h" using namespace std; namespace samples { void FixedVectorExample() { //기본 자료형을 넣을 때 FixedVector<int, 3> socres; scores.Add(10); scores.Add(50); cout<<"scores - <Size, Capacity> : " << "<" << scores.GetSize() << ", " << scores.GetCapacity() << ">" << endl; //개채를 넣을 때 FixedVector<IntVector, 5> intVectors; intVectors.Add(IntVector(2,5)); intVectors.Add(IntVector(4,30)); intVectors.Add(IntVector(22,3)); cout<<"scores - <Size, Capacity> : " << "<" << intVectors.GetSize() << ", " << intVectors.GetCapacity() << ">" << endl; //개채 포인터를 넣을 때 FixedVector<IntVector*, 4> intVectors2; IntVector* intVector = new IntVector(3, 2); intVectors2.Add(intVector); cout<<"scores - <Size, Capacity> : " << "<" << intVectors2.GetSize() << ", " << intVectors2.GetCapacity() << ">" << endl; delete intVector; } }
두 개의 템플릿 매개변수
pair
같은걸 만들어보자
//MyPair.h template<typename T> class MyPair { public: const T& GetFirest() const; const T& GetSecond() const; MyPair(const T& first, const T& second); private: T mFirst; T mSecond; } template<typename T> const T& MyPair<T>::GetFirst() const { return mFir st; } template<typename T> const T& MyPair<T>::GetSecond() const { return mSecond; } template<typename T> MyPair<T>::MyPair(const T& first, const T& second) : mFirst(first), mSecond(second) { }
//Main.cpp std::vector<MyPair<std::string> > students; students.emplace_back(MyPair<stdd::string>("a1234", "Coco")); students.emplace_back(MyPair<std::string>("a5678", "Mocha")); for (std::vector<MyPair<std::string> >::iterator it = students.begin();it!- students.end(); ++it) { std::cout<<it->GetFirst() <<" : "<< it->GetSecond() << std::endl; }
템플릿 매개변수를 하나 더 만들 수 있을까?( MyPair<std::string, int>
이런 식으로)
//MyPair.h template<typename T, typename U> class MyPair { public: const T& GetFirest() const; const U& GetSecond() const; MyPair(const T& first, const U& second); private: T mFirst; U mSecond; } template<typename T, typename U> const T& MyPair<T>::GetFirst() const { return mFirst; } template<typename T, typename U> const U& MyPair<T>::GetSecond() const { return mSecond; } template<typename T, typename U> MyPair<T>::MyPair(const T& first, const U& second) : mFirst(first), mSecond(second) { }
//Main.cpp std::vector<MyPair<std::string, int> > students; students.emplace_back(MyPair<stdd::string, int>("a1234", "Coco")); students.emplace_back(MyPair<std::string, int>("a5678", "Mocha")); for (std::vector<MyPair<std::string, int> >::iterator it = students.begin();it!- students.end(); ++it) { std::cout<<it->GetFirst() <<" : "<< it->GetSecond() << std::endl; }
템플릿 특수화
템플릿 특수화
-
특정한 템플릿 매개변수를 받도록 템플릿 코드를 커스터마이즈 할 수 있음
-
std::vector
에 좋은 예시가 있다template<class T, class Allocator> class std::vector<T, Allocator> {} //모든 형을 받는 제네릭 vector template<class Allocator> class std::vector<bool, Allocator> //bool을 받도록 특수화된 vector
bool은 왜 다를까?
- 이렇게 생각해보자
- 제네릭 템플릿이 어느 형에나 적용됨
- 단, 특정 형에 특수화된 템플릿이 있다면 그 특수화된 템플릿이 적용됨
- 그렇게 자주 쓰이진 않음
-
하지만
std::vector<bool>
특수화는 가치가 좀 있음- 메모리가 쪼들리는 플랫폼이라면
템플릿 특수화를 사용하는 예시
-
POWER함수
// Mymath.h template<typename V, typename EXP> V Poser(const V value, EXP exponent) { V result = 1; //int형을 기준으로 계산 while (exponent-- > 0) { result *= value; } return result; }
// Main.cpp int main() { int powerReulstInt = Power(10, 2); float powerResultFloat = Power(10.f, 2.5f); }
-
함수 특수화
// MyMath.h template<typename V, typename EXP> V Poser(const V value, EXP exponent) { V result = 1; //int형을 기준으로 계산 while (exponent-- > 0) { result *= value; } return result; }
// Main.cpp template<> float Power(float value, float exp) { return std::powf(value, exp); } int main() { int powerReulstInt = Power(10, 2); float powerResultFloat = Power(10.f, 2.5f); }
템플릿 특수화
-
전체 템플릿 특수화
- 템플릿 매개변수 리스트가 비어있음
template<typename VAL, typename EXP> VAL Power(const VAL value, EXP exponent){} // 모든 형을 받는 제네릭 power template<> float Power(float value, float exp) //float을 받도록 특수화된 power()
-
부분 템플릿 특수화
template<class T, class Allocator> class std::vector<T, Allocator> {} //모든 형을 받는 제네릭 vector template<class Allocator> class std::vector<bool, Allocator> //bool을 바도록 특수화된 vector
클래스 템플릿 특수화
//정의는 생략 template<class T> class MyArray { public: bool Add(T data); MyArray(); private: enum{MAX = 3}; int mSize; T mArray[MAX]; }; template<> class MyArray<bool> { public: bool Add(T data); MyArray(); private: enum{MAX = 3}; int mSize; T mArray[MAX] } MyArrat<bool>::MyArray() : mSize(0), mArray(0) { } bool MyArray<bool>::Add(bool data) { if (mSize >= MAX) { return false; } if(data) { mArray |= (1 << mSize++); } else { mArray &= ~(1 << mSize++); } return true; }
템플릿 프로그램의 장점과 단점
템플릿 프로그램의 장점과 단점
- 컴파일러가 컴파일 도중에 각 템플릿 인스턴스에 대한 코드를 만들어 줌
- 컴파일 타임은 비교적 느림
- 텐플릿 매개변수를 추가할 수록 더 느려짐
- 하지만 런타임 속도는 더 빠를지도 모름
- 실행파일의 크기가 커지기 때문에 항상 그런 것은 아님
- C#과 Java도 어느 정도 해당하는 말(그래서 ArrayList를 쓰지 말라는 것)
- 자료형만 다른 중복 코드를 없애는 훌륭한 방법
- 하지만 쓸모없는 템플릿 변형을 막을 방법이 없음
- 최대한 제네릭 함수를 짧게 유지할 것
- 제네릭이 아니어도 되는 부분은 별도의 함수로 옮기는 것도 방법. 이 함수가 인라인이 될 수도 있음
- 컴파일 도중에 다형성을 부여할 수 있음
- C++프로그래머가 너무 많이 잘못 썼던 기술
- 가상 테이블이 없어서 프로그램이 더 빠름
- 하지만 exe파일이 커지면 느려질 수 있음
- 코드 읽기가 더 힘듦
- 디버깅이 좀 이상할 수 있음
템플릿 프로그래밍 베스트 프랙티스
- 컨테이너의 경우 아주 적합
- 아주 다양한 형들을 저장할 수 있음
- 그런 이유로 Java와 C#의 제네릭이 주로 컨테이너에 쓰이는 것
- 컨테이너가 아닌 경우
- 각기 다른 서넛 이상의 자료형을 다룬다면 템플릿을 쓰자
- 두가지 정도라면 그냥 클래스를 두개 만들자