C++ - 템플릿

mohadang·2022년 10월 9일
0

C++

목록 보기
18/48
post-thumbnail

템플릿

  • std::vector scores;
    • 컴파일러가 vector를 읽어서 자동으로 만듬
  • 템플릿은 코드를 자료형마다 중복으로 작성하지 않아도 됨
  • 컴파일 도중에 실제 필요한 코드를 컴파일러가 작성해서 실행 되게끔 만들 수 있다.
  • 템플릿 함수 정의
template <typename T>
T Add(T a, T b)
{
  return a + b;
}
  • 템플릿 함수 사용
Add<int>(3,10);
//컴파일 시간에 템플릿 함수를 보고 필요한 함수 생성
int Add(int a, int b)
{
  return a + b;
}
  • 템플릿에 넣는 자료형 가짓수에 비례해서 exe 파일 크기 증가
    • 왜냐하면 그만큼 함수가 추가 되니까

함수 템플릿

template <typename T>
T Add(T a, T b)
{
  return a + b;
}

template <class T>
T Add(T a, T b)
{
  return a + b;
}
  • "함수 템플릿을 호출할 때 템플릿 매개변수를 생략할 수 있음."
    • 안 좋은 방식 같음... 실수를 야기 할 수 있음.
Add<int>(3, 10);
Add(3,10)
// 2 인자 모두 int니까 추측이 가능함.

템플릿 클래스


//이렇게 하면 템플릿화 된 인스턴스를 만든다(오브젝트의 인스턴스가 아니다.)
template<typename T>
class MyArray
{
public:
  bool Add(T data)
  MyArray();
private:
  enum { MAX = 3};
  T mArray[MAX];
};

template<typename T>
bool MyArray<T>::Add(T data)
{
  if(mSize >= MAX)
  {
    return false;
  }

  mArray[mSize++] = data;
  return true;
}

template<typename T>// 생성자에도 표시 해야 한다.
MyArray<T>::MyArray()// 생성자에도 표시 해야 한다.
: mSize(0)
{
}

MyArray<int> scores;
scores.Add(10);
  • 함수 템플릿을 사용할 때 처럼 매개변수 생략이 불가능
MyArray<int> scores;
MyArray scores;// Error
// 컴파일러가 알 수 있는 방법이 없다, 추측할 수 있는 인자도 없는데 어떻게....

템플릿 프로그래밍 사용시 주의

  • '템플릿으로 프로그래밍을 하면 컴파일 에러가 굉장히 복잡해 진다.'
  • '템플릿 클래스의 구현은 헤더 파일에 같이 존재 해야 한다. 인라인 함수와 마찬가지이다.'
    • 컴파일러가 main.cpp를 컴파일할 때, MyArray.cpp를 못 찾는다.
      • 컴파일 타입에 MyArray 클래스들을 만들어야 함
      • MyArray.h를 통해서 오직 MyArray 클래스 선언만 볼 수 있다.
    • 따라서, 컴파일러가 MyArray를 보고 클래스를 만들어 줄 수 없다.
      • 템플릿 함수는 컴파일 시간에 만들어야 하는데 헤더에 구현이 없으면 어떻게 구현을 만들까...??
    • 해결책은 헤더파일에 구현을 두어야 한다.
  • '템플릿으로 프로그래밍을 하게 되면 컴파일 시간이 오래 걸린다'
    1) 컴파일 시간에 함수를 만들기 때문에
    2) '왜 cpp와 헤더를 나누는가???'
    • cpp나 헤더 파일이 변경 사항이 없다면 컴파일을 건너 뛰지만 변경 사항이 있다면 재 컴파일을 해야 한다.
    • 헤더파일을 바꾸는 것과 cpp 파일을 바꾸는 것에 따라 컴파일 시간의 변경이 엉청나게 차이 난다.
      • cpp 파일을 바꾸면 해당 cpp 파일만 재 컴파일 하면 된다.
      • "헤더 파일을 바꾸면 헤더 파일을 include 하고있는 파일들도 재 컴파일 해야 한다."
        "의존성에 따라 재 컴파일 연쇄 반응이 일어나게 된다."
    • 템플릿을 사용하게 되면 구현이 헤더파일에 있게 됨으로 헤더 파일이 바뀔 수 있는 상황이 더 자주 격는다.
  • 이런 오류가 나면...
    • unresolved external symbol ...
    • 템플릿 구현이 헤더파일에 있지 않아서 main.cpp에서 참조하지를 못한다.
  • 템플릿을 불필요하게 남용하면 안된다.

클래스 템플릿 트릭

template<typename T, size_t N>// N 은 size_t 타입만 가능
class FixedVector
{
public:
private:
  T mArray[N];
};

// 템플릿으로 배열의 갯수를 정의할 수 있다. 그것도 컴파일 타임에...
// 이렇게 하면 배열의 갯수를 강제 할 수 있다.
FixedVector<int, 16> numbers; 
  • 이렇게 반박 할 수도...
    • 16이라는 배열 갯수가 달라질 때마다 클래스를 새로 만듬??? 낭비 아님??
      • 어쨋든 배열 갯수가 달라지는 코드는 무조건 작성하게 되어 있다.
      • 그때마다 배열 길이, 범위 체크를 16이라는 상수로 일일히 검사하는 것 보다는 났다.
    • 그런데 이럴바에야 const int arraySize; 를 두고 초기화 리스트로 초기화 해서 사용 하는것이 좋지 않을까??
    • 위의 코드는 정말 배열처럼 상수를 이용햐여 고정 시킬때만 의미 있을 것 같다.
      • 런타임시 길이를 지정하는것은 불가능 하다.

복수의 템플릿 매개변수

template<typename T, typename U>
void Print(const T& a, const U& b)
{
  cout << a << "/" << b << endl;
}

template<typename T, typename U>
class MyPair
{
public:
  T mFirst;
  U mSecond;
}

빈 템플릿 매개변수

template<>
float Power(float value, float exp)
{
  return std::powf(value, exp);
}

Power<float,float>(3.14, 3.5); 호출할 때 위 함수를 호출 함
  • 템플릿 특수화 때 필요

템플릿 특수화(Specialization)

  • 그렇게 자주 사용하지는 않음.

  • 템플릿(C++) vs Generic(C#)

    • Generic, 일반화
  • 특정한 템플릿 매개변수를 받도록 템플릿 코드를 커스터마이즈할 수 있음

  • std::vector에 좋은 예가 있음.

template <class T, class Allocator>
class std::vector<T, Allocator>// 모든 형을 받는 제네릭 vector
{

}
// 일반적인 vector를 사용할 때는 이 틀을 사용한다.
template <class Allocator>
class std::vector<bool, Allocator>// bool을 받도록 특수화된 vector
{

}
// bool 타입의 vector를 사용할 때는 이 틀을 사용한다.
// vector<bool> data;
  • bool은 왜 다를까??
    • "std::vector<bool> 특수화는 가치가 좀 있음"
    • 메모리가 쪼들리는 플랫폼에서 --> 임베디드
template<typename V, typename EXP>
V Power(const V value, EXP exponent)
{
  V result = 1;
  while(exponent-- > 0)
  {
    result *= value;
  }

  return result;
}
  • float powerResultFloat = Power(10.f, 2.5f)
    • 결과는 1000, 틀린 값이다. 정답은 316.22777... 이다.
    • 위의 Power 함수는 int 정수형을 기준으로 만들어 졌다.
    • float, double 과 같은 실수형을 위한 함수가 따로 필요
  • 템플릿 특수화를 사용하여 실수형을 위한 함수 생성
template<>
float Power(float value, float exp)
{
  return std::powf(value, exp);
}

전체 템플릿 특수화

  • 모든 형을 받는 제네릭
template<typename V, typename EXP>
V Power(const V value, EXP exponent)
{

}
  • float 을 받도록 특수화
template<>
float Power(float value, float exp)
{

}

부분 템플릿 특수화

  • 모든 형을 받는 제네릭
template <class T, class Allocator>
class std::vector<T, Allocator>// 모든 형을 받는 제네릭 vector
  • bool을 받도록 특수화
template <class Allocator>
class std::vector<bool, Allocator>

클래스 템플릿 특수화

  • 전체
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(bool data);
  MyArray();
private:
  enum {MAX = 3};
  int mSize;
  int mArray;
}

MyArray<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++);
  }
}

템플릿 프로그래밍의 장점, 단점

  • 컴파일러가 컴파일 도중에 각 템플릿 인스턴스에 대한 코드를 만들어 줌
  • 컴파일 타임은 비교적 느림
  • 템플릿 매개변수를 추가할 수록 더 느려짐
    • std::pair<std::string, int>
    • std::pair<std::string, std::string>
    • std::pair<float, std::string>
  • 하지만 런타임 속도는 더 빠를지도 모름
  • 실행 파일 크기가 커지기 때문에 항상 그런 것은 아님(캐시 효용성)
  • C# 과 Java도 어느 정도 해당되는 말 (그래서 ArrayList를 쓰지 말라는 것)
    • Boxing, UnBoxing.
    • int형도 오브젝트 형으로 만들어서 저장
  • 자료형만 다른 중복 코드를 없애는 훌륭한 방법
    • 크래서 container에서는 발군임
  • 하지만 쓸모없는 템플릿 변형을 막을 방법이 없음
    • 이런게 전부 필요한가???
      • Add();
      • Add();
      • Add();
    • 또는 이런게
      • FixedVector<3>();// 템플릿 매개변수는 요소의 수
      • FixedVector<4>();
      • FixedVector<15>();
  • 최대한 템플릿 함수의 내용 길이를 짧게 유지할 것
    • 이렇게 하면 컴파일시 코드 복붙양이 줄어든다.
  • 템플릿이 아니어도 되는 부분은 별도의 함수로 옮기는 것도 방법. 이 함수가 인라인 될 수도 있음.
  • 컴파일 도중에 다형성을 부여할 수 있음
    • 잘못 사용하는 것임.
    • C++ 프로그래머가 너무 많이 잘못 썼던 기능
    • 가상 테이블이 없어서 프로그램이 더 빠름
    • 하지만 exe파일이 커지면 느려질 수 있음
  • 코드 읽기가 더 힘듦.
  • 디버깅이 좀 이상할 수 있음
    -더 이상 큰 문제는 아니지만..

베스트 프랙티스

  • 컨테이너의 경우 매우 적합
    • 아주 다양한 형들을 저장할 수 있음
    • 그런 이유로 Java와 C# 제네릭이 주로 컨테이너에 쓰이는 것
  • 컨테이너가 아닌 경우
    • 각기 다른 서넛 이상의 자료형을 다룬다면 템플릿을 쓰자
    • 두 가지 정도라면 그냥 클래스를 2개 만들자.
      • class Math;
      • class FloatMath;

메타 프로그래밍

  • 프로그래밍은 아니고 방법론

  • 실행중에 수행되어야 할 일을 템플릿으로 작성하여 함수 호출을 없애고 함수가 반환해야할 결과 값을 컴파일 중에 얻어옴

  • EX)

template<typename T>
class MyArray
{
public:
  MyArray();

  /*
  const T& 로 받는 이유는 T가 개체일 수 있어서, 
  T가 만약 10m 넘는 개체라면 이를 복사하기란 무리이다.
  물론 배열에 저장 할 때는 복사해서 저장할 것이지만
  적어도 함수의 매개 변수로 받을 때는 복사를 막기 위해서 const T& 받음
  */
  bool Add(const T& data);
                      
  size_t GetSize() const;

private:
  enum { MAX = 3 };// 괜찮은 트릭

  size_t mSize;
  T mArray[MAX];
};

template<typename T>
MyArray<T>::MyArray()
  : mSize(0)
{
}

template<typename T>
size_t MyArray<T>::GetSize() const
{
  return mSize;
}

template<typename T>
bool MyArray<T>::Add(const T& data)
{
  if (mSize >= MAX)
  {
    return false;
  }

  mArray[mSize++] = data;

  return true;
}
  • EX)
template<typename T, size_t N>
class FixedVector
{
public:
  FixedVector();

  bool Add(const T& data);
  size_t GetSize() const;
  size_t GetCapacity() const;

private:
  size_t mSize;
  T mArray[N];// enum 트릭이 없다.
};

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를 넣어서 사용자가 capacity를 너무 적게 넣었다는 것을 
    // 빨리 알릴 수 있다.(현명한 Exception 처리)
    return false;
  }

  mArray[mSize++] = data;
  return true;
}
  • EX)
template <typename T>
static T Divide(T value1, T value2)
{
  return value1 / value2;// value2가 0인것 어떻게 확인???
}
  • EX)
template<>
class MyArray<bool>
{
public:
  bool Add(bool data);
  MyArray();
private:
  enum {MAX = 3};
  int mSize;
  int mArray;
}

MyArray<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++);
  }
}
profile
mohadang

0개의 댓글