씹어먹는 C++
10장 c++ 템플릿 410p-493p
원하는 타입으로 대체 되어 사용됨
정의되는 클래스에 대해 템플릿을 정의하고, 템플릿 인자로 T를 받아 해당 타입인 것을 명시
template <typename T>
template <class T>
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
특정 경우에 대해서 따로 처리하는 것
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> {
... // 원하는 코드
}
함수 템플릿
원하는 타입을 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
함수는 아니지만 함수 인 척을 하는 객체
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);
하나의 인자로 템플릿을 통해 넘김
템플릿 인자로 전달할 수 있는 타입들
- 정수 타입들 (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>
0 개 이상의 템플릿 인자들을 가변 길이로 받아 드림
template <typename T, typename... Types>
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
return (... + nums);
해당 형식을 컴파일러가 아래 처럼 해석
return ((((1 + 4) + 2) + 3) + 10);
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
bool 1 bytes < int 4bytes를 bool 32bits로 사용하여 효율적
N 번째 bool 데이터는 N / 32 번째 int에 존재, N % 32 번째 비트
특정 비트에만 선택적으로 1로 변환으로 특화
주변 나머지 비트들의 값은 보존하면서 특정 비트만 1
원하는 위치의 1 을 AND 하게 되면 해당 비트의 값이 무엇 파악 가능
해당 위치에 있는 비트가 1 일 때 에만 저 값이 0 이 아니게 되고 0
이면 저 값 전체가 0
자기 자신을 가리키는 타입
자기 자신을 가리키는 타입
typedef와 같은 역할로 보다 직관적
typedef Ratio_add<rat, rat2> rat3;
using rat3 = Ratio_add<rat, rat2>;
컴파일러가 타입을 정확히 알아낼 수 있는 경우 굳이 그 길고 긴 타입을 적지 않고 간단히 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