C++ 템플릿

SOEUN CHOI·2022년 5월 31일
0

C++_study

목록 보기
7/15

씹어먹는 C++

10장 c++ 템플릿 410p-493p


template

원하는 타입으로 대체 되어 사용됨

클래스 템플릿

정의되는 클래스에 대해 템플릿을 정의하고, 템플릿 인자로 T를 받아 해당 타입인 것을 명시
template <typename T>
template <class T>

  • 클래스 템플릿 인스턴스화 (class template instantiation)
    클래스 템플릿에 인자를 전달해서 실제 코드를 생성하는 것

example code

template <typename T>
class Vector {
	T* data;
	int capacity;
	int length;
	public:
	// 생성자
		Vector(int n = 1) : data(new T[n]), capacity(n), length(0) {}
	// 맨 뒤에 새로운 원소를 추가한다.
		void push_back(T s) {
			if (capacity <= length) {
				T* temp = new T[capacity * 2];
				for (int i = 0; i < length; i++) {
					temp[i] = data[i];
				}
				delete[] data;
				data = temp;
				capacity *= 2;
			}
			data[length] = s;
			length++;
		}
	// 임의의 위치의 원소에 접근한다.
	T operator[](int i) { return data[i]; }
	// x 번째 위치한 원소를 제거한다.
	void remove(int x) {
		for (int i = x + 1; i < length; i++) {
			data[i - 1] = data[i];
		}
		length--;
	}	
	// 현재 벡터의 크기를 구한다.
	int size() { return length; }
		~Vector() {
		if (data) {
			delete[] data;
		}
	}
};


int main() {
  // int 를 보관하는 벡터를 만든다.
  Vector<int> int_vec;
  int_vec.push_back(3);
  int_vec.push_back(2);
  
  std::cout << "-------- int vector ----------" << std::endl;
  std::cout << "첫번째 원소 : " << int_vec[0] << std::endl;
  std::cout << "두번째 원소 : " << int_vec[1] << std::endl;
  
  Vector<std::string> str_vec;
  str_vec.push_back("hello");
  str_vec.push_back("world");
  
  std::cout << "-------- std::string vector -------" << std::endl;
  std::cout << "첫번째 원소 : " << str_vec[0] << std::endl;
  std::cout << "두번째 원소 : " << str_vec[1] << std::endl;

-------- int vector ----------
첫번째 원소 : 3
두번째 원소 : 2
-------- std::string vector -------
첫번째 원소 : hello
두번째 원소 : world

템플릿 특수화 (template specialization)

특정 경우에 대해서 따로 처리하는 것

  • 템플릿 부분 특수화 시에 반드시 다른 연산자가 붙지 않고 단순한 식별자만 입력해야함

example code

template <typename A, typename B, typename C>
class test {};

template <typename B>
class test<int, B, double> {};

template <>
class test<int, int, double> {};

//전달하는 템플릿 인자가 없어도 특수화를 원할시 template <>
template <>
class Vector<bool> {
... // 원하는 코드
}

함수 템플릿 (Function template)

함수 템플릿

원하는 타입을 T로 처리 이후 인스턴스화를 통해 사용
클래스를 인스턴스화 했을 때 와는 다르게 <> 사용하지 않음
들어온 파라미터의 타입을 파악하여 인스턴스

example code

template <typename T>
  T max(T& a, T& b) {
  return a > b ? a : b;
}
int main() {
  int a = 1, b = 2;
  
  std::cout << "Max (" << a << "," << b << ") ? : " << max(a, b) << std::endl;
  std::string s = "hello", t = "world";
  std::cout << "Max (" << s << "," << t << ") ? : " << max(s, t) << std::endl;
}

Max (1,2) ? : 2
Max (hello,world) ? : world

함수 객체(Function Object - Functor)

함수는 아니지만 함수 인 척을 하는 객체

example code


struct Comp1 {
	bool operator()(int a, int b) { return a > b; }
};

struct Comp2 {
	bool operator()(int a, int b) { return a < b; }
};

template <typename Cont, typename Comp>
void bubble_sort(Cont& cont, Comp& comp) {
  for (int i = 0; i < cont.size(); i++) {
    for (int j = i + 1; j < cont.size(); j++) {
    //comp가 객체인데 함수처럼 쓰임
      if (!comp(cont[i], cont[j])) {
      cont.swap(i, j);
      }
    }
  }
}

// 사용 예제

Comp2 comp2;
bubble_sort(int_vec, comp2);

타입이 아닌 템플릿 인자 (non-type template arguments)

하나의 인자로 템플릿을 통해 넘김

  • 템플릿 인자로 전달할 수 있는 타입들
    - 정수 타입들 (bool, char, int, long 등등). 당연히 float 과 double 은 제외
    - 포인터 타입
    - enum 타입
    - std::nullptr_t (널 포인터)

  • 컴파일 타입에 값들이 정해져야 하는 것들를 주로 사용

  • 템플릿 인자로 배열의 크기를 명시하여 함수에 배열을 전달할 때 배열의 크기에 대한 정보 확인

  • 의존 타입(dependent type)
    템플릿 인자에 따라서 어떠한 타입이 달라질수 있음

example code

template <typename T, int num>
T add_num(T t) {
	return t + num;
}

int main() {
  int x = 3;
  std::cout << "x : " << add_num<int, 5>(x) << std::endl;
}

template 의 인자로 T 를 받고, 추가적으로 마치 함수의 인자 처럼 int num 사용

x : 8

디폴트 템플릿 인자

디폴트로 바뀌지 않는 템플릿 인자
= 통해 디폴트 값 지정
template <typename T, int num = 5>

가변 길이 템플릿

템플릿 파리미터 팩(parameter pack)

0 개 이상의 템플릿 인자들을 가변 길이로 받아 드림

template <typename T, typename... Types>

  • 템플릿 파라미터 팩과 함수 파라미터 팩의 차이점
    템플릿의 경우 타입 앞 에 ...
    함수의 경우 타입 뒤 에 ...
  • sizeof...()
    전체 인자의 개수 리턴

example code

template <typename T>
void print(T arg) {
	std::cout << arg << std::endl;
}

template <typename T, typename... Types>
void print(T arg, Types... args) {
  std::cout << arg << ", ";
  print(args...);
}

int main() {
  print(1, 3.1, "abc");
  print(1, 2, 3, 4, 5, 6, 7);
}

1, 3.1, abc
1, 2, 3, 4, 5, 6, 7

Fold Expression

return (... + nums);
해당 형식을 컴파일러가 아래 처럼 해석
return ((((1 + 4) + 2) + 3) + 10);

  • 단항 좌측 Fold (Unary left fold) 4가지 존재

템플릿 메타 프로그래밍

Template Meta Programming - TMP
타입을 가지고 컴파일 타임에 생성되는 코드로 짜는 프로그래밍

  • 컴파일 타임에 모든 연산이 끝나기 때문에 프로그램 실행 속도를 향상 시킬 수 있다는 장점

  • 템플릿 메타 프로그래밍은 매우 복잡하니 유의

  • TMP 를 통해서 컴파일 타임에 debugging 가능 (Ex. 단위나 통화 일치 여부등등)

  • 런타임에서 찾아야 하는 오류를 컴파일 타임에서 미리 다 잡을 수 있음

  • 속도가 매우 중요한 프로그램의 경우 TMP 를 통해서 런타임 속도 향상 가능

  • 단위(Unit) 라이브러리
    세분화하여 타입을 정의하고 계산할때 TMP사용

example code

 /* 컴파일 타임 팩토리얼 계산 */
#include <iostream>

template <int N>
struct Factorial {
	static const int result = N * Factorial<N - 1>::result;
};

template <>
struct Factorial<1> {
	static const int result = 1;
};
 
int main() {
  std::cout << "6! = 1*2*3*4*5*6 = " << Factorial<6>::result << std::endl;
}

6! = 1*2*3*4*5*6 = 720

Tips

bool 처리

bool 1 bytes < int 4bytes를 bool 32bits로 사용하여 효율적
N 번째 bool 데이터는 N / 32 번째 int에 존재, N % 32 번째 비트

or 연산

특정 비트에만 선택적으로 1로 변환으로 특화
주변 나머지 비트들의 값은 보존하면서 특정 비트만 1

and 연산

원하는 위치의 1 을 AND 하게 되면 해당 비트의 값이 무엇 파악 가능
해당 위치에 있는 비트가 1 일 때 에만 저 값이 0 이 아니게 되고 0
이면 저 값 전체가 0

typedef

자기 자신을 가리키는 타입

using

자기 자신을 가리키는 타입
typedef와 같은 역할로 보다 직관적

typedef Ratio_add<rat, rat2> rat3;
using rat3 = Ratio_add<rat, rat2>;

auto 키워드

컴파일러가 타입을 정확히 알아낼 수 있는 경우 굳이 그 길고 긴 타입을 적지 않고 간단히 auto 로 표현

example code


int sum(int a, int b) { return a + b; }

class SomeClass {
  int data;
  public:
    SomeClass(int d) : data(d) {}
    SomeClass(const SomeClass& s) : data(s.data) {}
};

int main() {
  // 함수 리턴 타입으로 부터 int 라고 추측 가능
  auto c = sum(1, 2); 
  // double 로 추측 가능!
  auto num = 1.0 + 2.0;
  
  // 해당 객체로 추측
  SomeClass some(10);
  auto some2 = some;
  
  //그냥 int라 추측됨
  auto some3(10); 
  
	std::cout << "c 의 타입은? :: " << typeid(c).name() << std::endl;
	std::cout << "num 의 타입은? :: " << typeid(num).name() << std::endl;
	std::cout << "some2 의 타입은? :: " << typeid(some2).name() << std::endl;
	std::cout << "some3 의 타입은? :: " << typeid(some3).name() << std::endl;
}

c 의 타입은? :: i
num 의 타입은? :: d
some2 의 타입은? :: 9SomeClass
some3 의 타입은? :: i

profile
soeun choi

0개의 댓글