C++ Template Programming - Template Design[7]
이번에는 템플릿 코드의 메모리 사용량을 줄이는 방법, CRTP, SFINAE typelinst 등에 대해서 살펴보겠습니다.
Thin template
템플릿을 사용하면 소스코드 용량이 늘어나게됩니다.
// 멤버 함수 4개 * 3개 타입 => 12개 함수 생성
template<typename T> class Vector
{
T* buff;
int sz;
public:
int size() const;
bool empty() const;
void push_front(const T& a);
T& front();
};
int main()
{
Vector<int> v1;
Vector<short> v2;
Vector<double> v3;
}
소스코드 용량을 줄이는 방법1
// 방법 1. template parameter T를 사용하지 않은 멤버 함수는 기반 클래스로 옮기자
// 멤버 함수 2개 * 3개 타입 + 기반 클래스 멤버 함수 2개 => 8개
class VectorBase
{
protected:
int sz;
public:
int size() const;
bool empty() const;
};
template<typename T> class Vector : public VectorBase
{
T* buff;
public:
void push_front(const T& a);
T& front();
};
int main()
{
Vector<int> v1;
Vector<short> v2;
Vector<double> v3;
}
소스코드 용량을 줄이는 방법2 -> Thin template
// 방법 2. void* 기반 컨테이너 + 캐스팅을 책임지는 파생 클래스
// 멤버 함수 2개 * 3개 타입 + 기반 클래스 멤버 함수 2개 => 8개
class VectorBase
{
protected:
void* buff;
int sz;
public:
int size() const;
bool empty() const;
void push_front(const void* a);
void* front();
};
// 캐스팅만 책임지는 파생 클래스
template<typename T> class Vector : public VectorBase
{
public:
inline int size() const { return VectorBase::size(); }
inline bool empty() const { return VectorBase::empty(); }
inline void push_front(const T& a) { VectorBase::push_front(static_cast<void*>(a)); }
inline T& front() { return static_casst<T&>(VectorBase::front()); }
};
int main()
{
Vector<int> v1;
Vector<short> v2;
Vector<double> v3;
}
CRTP : Curiously Recurring Template Patten
기반 클래스(부모, 과거)에서 상속된 파생 클래스(자식, 미래)의 이름을 사용 할 수 있는 방법.
파생 클래스를 만들 때 기반 클래스의 템플릿 인자로 자신의 이름을 전달하여 구현
- CRTP 활용 1 : 상속 관계에서 가상함수(virtual)을 사용하지 않고 다형성 구현 가능 -> 장점 : 가상함수가 수백개 일 경우 메모리 사용량이 증가함
#include <iostream>
using namespace std;
// CRTP : Curiously Reccuring Template Pattern
template<typename T> class Window
{
public:
void msgLoop() // void msgLoop(Window* this)
{
static_cast<T*>(this)->onKeyDown(); // T를 사용해서 자식의 onKeyDown 호출, virutal 없이 가상함수 처럼 동작
}
void onKeyDown() { cout << "Window onKeyDown" << endl; }
};
class MyWindow : public Window<MyWindow> // 상속시 T에 자기 이름 전달
{
public:
void onKeyDown() { cout << "Window onKeyDown" << endl; }
};
int main()
{
MyWindow w;
w.msgLoop();
}
- CRTP 활용 2 : Singleton 패턴 구현
#include <iostream>
using namespace std;
template<typename T> class Singleton
{
public:
static T* instance;
static T& getInstance()
{
if (instance == 0)
instance = new T;
return *instance;
}
};
template<typename T> T* Singleton<T>::instance = 0;
class Cursor : public Singleton<Cursor>
{
};
int main()
{
Cursor& c = Cursor::getInstance();
}
- CRTP 활용 3 : static 변수 적용 범위를 파생 클래스의 이름으로 그룹화 해서 관리하기
#include <iostream>
using namespace std;
template<typename T> struct Count
{
private:
static int cnt;
public:
Count() { ++cnt; }
~Count() { --cnt; }
static int count() { return cnt; }
};
template<typename T> int Count<T>::cnt = 0;
class Mouse : public Count<Mouse>
{
};
class Keyboard : public Count<Keyboard>
{
};
int main()
{
Mouse m1, m2;
Keyboard k1, k2;
cout << k1.count() << endl;
}
Policy-based design
-
클래스가 사용하는 정책을 성능저하 없이 교체 할 수 있음, 이 기법을 사용하는 대표적인 라이브러리 -> “STL”
-
성능 저하 없이 정책을 교체 할 수 있음
- List를 제공 할 때 lock 사용 유/무 버전을 사용자가 선택 할 수 있도록 제공 하도록 구현 예제
template<typename T, typename ThreadModel> class List
{
ThreadModel tm;
public:
void push_front(const T& a)
{
tm.Lock();
//......
tm.Unlock();
}
};
// 동기화 정책을 담은 정책 클래스 : 반드시 Lock()/Unlock() 이 있어야 한다.
struct NoLock
{
inline void Lock() {}
inline void Unlock() {}
};
struct Win32Lock
{
inline void Lock() { } // implement lock using win32 api
inline void Unlock() { }
};
struct LinuxLock
{
inline void Lock() { } // implement lock using linux system call
inline void Unlock() { }
};
List<int, NoLock> st; // NoLock 버전 제공
int main()
{
st.push_front(10);
}
- 메모리 할당 방식을 사용자가 선택 할 수 있도록 제공하는 예제
#include <iostream>
#include <vector>
#include <limits>
using namespace std;
template <class T> class ecAlloc
{
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
// policy clone 을 위한 도구. "rebind" 동영상 참고.
template <class U> struct rebind
{
typedef ecAlloc<U> other;
};
pointer address(reference value) const { return &value; }
const_pointer address(const_reference value) const { return &value; }
ecAlloc() noexcept { }
ecAlloc(const ecAlloc&) noexcept { }
~ecAlloc() noexcept { }
template <class U> ecAlloc(const ecAlloc<U>&) noexcept {}
size_type max_size() const throw() {
return numeric_limits<std::size_t>::max() / sizeof(T);
}
// 메모리만 할당하는 함수. 초기화(생성자 호출) 하지 않습니다.
pointer allocate(size_type num, const void* = 0)
{
cerr << "allocate " << num << " element(s)"
<< " of size " << sizeof(T) << endl;
pointer ret = (pointer)(::operator new(num * sizeof(T)));
cerr << " allocated at: " << (void*)ret << endl;
return ret;
}
// 초기화(생성자 호출) 함수. Placement new를 사용합니다.
void construct(pointer p, const T& value) {
new((void*)p)T(value);
}
// 객체 파괴(소멸자 호출) 함수
void destroy(pointer p) {
p->~T();
}
// 메모리 해지
void deallocate(pointer p, size_type num) {
cerr << "deallocate " << num << " element(s)"
<< " of size " << sizeof(T)
<< " at: " << (void*)p << endl;
::operator delete((void*)p);
}
};
// return that all specializations of this allocator are interchangeable
template <class T1, class T2>
bool operator== (const ecAlloc<T1>&, const ecAlloc<T2>&) noexcept
{
return true;
}
template <class T1, class T2>
bool operator!= (const ecAlloc<T1>&, const ecAlloc<T2>&) noexcept
{
return false;
}
int main()
{
vector<int, ecAlloc<int>> v(2, 0);
v.resize(4);
}
SFIAE : Subsitution Failure is Not An Error
- 컴파일러가 이름으로 함수를 찾는 순서 : exactly matching -> template -> variable argument
- 함수 템플릿을 사용시 T의 타입이 결정되고 함수를 생성하려고 할 때 리턴 타입이나 함수 인자 등에서 치환에 실패하면 컴파일 에러가 아니라, 함수 후보군에서 제외
- 동일한 이름의 다른 함수가 있다면 그 함수를 사용하게 됨
template<typename T>
typename T::type foo(T t) //int::type foo(int t)
{
cout << "T" << endl;
return 0;
}
void foo(...) { cout << "..." << endl; }
int main()
{
foo(3); // output : ...
}
-
특정 타입에만 적용되는 템플릿을 구현하고 싶을 때
-
static_assert를 활용 하여 구현 : 조건을 만족하지 않으면 컴파일 에러
-
enable_if 방식으로 구현 : 조건을 만족하지 않으면 함수를 생성하지 않고, 동일 이름의 다른 함수가 있으면 사용
-
#include <type_traits>
using namespace std;
// foo 함수를 정수 계열에 대해서만 코드 생성되게 하고 싶다.
// 방법 1. static_assert
// 특징 : T가 정수가 아니면 무조건 error 발생.
template<typename T> void foo(T a)
{
static_assert(is_integral<T>::value, "error");
}
// 방법 2. enable_if
// 특징 : T가 정수가 아니면 error가 아니라 코드 생성을 하지 않음. 호출 가능한 다른 foo()가 있으면 사용됨
// enable_if 위치 1. 함수 리턴 타입에 적용
template<typename T> typename enable_if< is_integral<T>::value,void >::type foo(T a)
{
}
// enable_if 위치 2. 함수 인자 타입에 적용 - 생성자..
template<typename T>
void foo(T a, typename enable_if< is_integral<T>::value, void >::type* p = nullptr )
{
}
// enable_if 위치 3. 템플릿 인자에 적용
// template<typename T, void* = nullptr >
template<typename T, typename enable_if< is_integral<T>::value, void >::type* = nullptr >
void foo(T a)
{
}
void foo(...) {}
int main()
{
foo(3);
}
Member detect idioms
클래스 안에 특정한 멤버함수, 멤버 데이터, 멤버 타입 등이 있는지 조사하는 방법
- 특정 멤버 함수가 있는지 조사 방법
#include <iostream>
#include <vector>
#include <array>
#include <type_traits>
using namespace std;
template<typename T> struct has_resize
{
using YES = char;
using NO = short;
template<typename U> static YES check(typename std::add_pointer<decltype(U().resize(0))>::type a);
template<typename U> static NO check(...);
static constexpr bool value = (sizeof(check<T>(0)) == sizeof(YES));
};
int main()
{
cout << has_resize<vector<int>>::value << endl; // 1
cout << has_resize<array<int, 10>>::value << endl; // 0
}
- 멤버 변수가 value 타입을 갖고 있는지 여부 조사 방법
#include <iostream>
using namespace std;
struct NoValueType
{
};
struct HasValueType
{
typedef int value_type;
};
template<typename T> struct has_value_type
{
using YES = char;
using NO = short;
template<typename U> static YES check(typename U::value_type* a);
template<typename U> static NO check(...);
static constexpr bool value = (sizeof(check<T>(0)) == sizeof(YES));
};
int main()
{
cout << has_value_type<HasValueType>::value << endl; // 1
cout << has_value_type<NoValueType>::value << endl; // 0
}
Typelist
loki libaray의 Typelist의 구현 살펴보기
- type을 보관하는 list 형태 템플릿
// 1. 값을 보관하지 않는다.
// 2. N개의 타입을 보관한다.
// 3. NullType 과 매크로 도입
// Typelist : 타입을 여러개 보관하는 type의 list(값이 아님.)
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {}; // 모든 typelist의 끝은 NullType으로 표현
// 매크로 도입
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
int main()
{
Typelist<int, NullType> t1;
Typelist<int, Typelist<double, NullType>> t2;
//Typelist<int, Typelist<double, Typelist<char, NullType>>> t3;
TYPELIST_1(int) t4; //
TYPELIST_4(int, double, char, short) t4; //
}
- 활용, 가변 인자 템플릿 없이 튜플에 여러 타입을 전달 할 수 있음
#include <iostream>
using namespace std;
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
//-------------------------------------------
template<typename T> class xtuple {};
int main()
{
//xtuple<int> t1;
xtuple< TYPELIST_3(int, double, char) > t1;
}
- typelist 요소의 갯수 구하기
#include <iostream>
using namespace std;
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
//-----------------------------------------------------------------------------------------------------
// Typelist의 요소 갯수 구하기.
// 1. 사용하는 모습을 보고 primary template 작성.
template<typename T> struct Length;
//{
//};
// 2. 갯수를 구할수 있도록 부분 특수화
template<typename T, typename U> struct Length<Typelist<T, U>>
{
enum { value = Length<U>::value + 1 };
};
// 3. 재귀를 종료 하기 위한 전문화(특수화)
template<> struct Length<NullType>
{
enum { value = 0 };
};
template<typename T> void test()
{
cout << Length<T>::value << endl; // 4
}
int main()
{
test< TYPELIST_4(int, char, short, double) >();
}
- typelist의 at 구현 (n번째 요소의 타입)
#include <iostream>
using namespace std;
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
//-----------------------------------------------------------------------------------------------------
// Typelist의 N 번째 요소의 타입 구하기
// 1. 사용하는 코드를 보고 primary template 작성.
// T : Typelist
template<typename T, int N> struct TypeAt;
// 2. 원하는 타입을 구할수 있도록 부분특수화
// T : Typelist의 요소 타입
/*
template<typename T, typename U, int N> struct TypeAt<Typelist<T, U>, N>
{
typedef ? type;
};
*/
// N == 0 일때.
template<typename T, typename U> struct TypeAt<Typelist<T, U>, 0>
{
typedef T type;
};
// N != 0 일때.
template<typename T, typename U, int N> struct TypeAt<Typelist<T, U>, N>
{
typedef typename TypeAt<U, N-1>::type type;
};
template<typename T> void test()
{
typename TypeAt<T, 3>::type n; // char
cout << typeid(n).name() << endl;
}
int main()
{
test<TYPELIST_4(int, char, double, long)>();
}
- typelist에 type add하기
#include <iostream>
using namespace std;
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
//-------------------------------------------------------------------------------------------
// Typelist 끝에 타입 추가하기.
template<typename TL, typename T> struct Append;
// TL T
// 1. NullType, NullType => NullType
template<> struct Append<NullType, NullType>
{
typedef NullType type;
};
// 2. NullType, 임의의타입 => Typelist<임의의타입, NullType>
template<typename T> struct Append<NullType, T>
{
typedef Typelist<T, NullType> type;
};
// 3. NullType, Typelist<Head, Tail> => Typelist<Head, Tail>
template<typename Head, typename Tail> struct Append<NullType, Typelist<Head, Tail> >
{
typedef Typelist<Head, Tail> type;
};
// 4. Typelist<Head, Tail>, NullType => Typelist<Head, Tail>
// 이번 단계의 코드는 없어도 됩니다. 5단계의 코드만 있으면 됩니다.
template<typename Head, typename Tail> struct Append<Typelist<Head, Tail>, NullType >
{
typedef Typelist<Head, Tail> type;
};
// 5. Typelist<Head, Tail>, T => Typelist<Head, Append<Tail, T>::type>
template<typename Head, typename Tail, typename T> struct Append<Typelist<Head, Tail>, T >
{
typedef Typelist<Head, typename Append<Tail, T>::type> type;
};
template<typename T> void test()
{
typename Append<T, int>::type t1;
cout << typeid(t1).name() << endl; // int, char, double, int, NullType
}
int main()
{
test<TYPELIST_3(int, char, double)>();
}
- typelist 활용, GenScatterHierachy 구현 해보기
#include <iostream>
using namespace std;
template<typename T, typename U> struct Typelist
{
typedef T Head;
typedef U Tail;
};
struct NullType {};
#define TYPELIST_1(T1) Typelist<T1, NullType>
#define TYPELIST_2(T1, T2) Typelist<T1, Typelist<T2, NullType>>
#define TYPELIST_3(T1, T2, T3) Typelist<T1, Typelist<T2, Typelist<T3, NullType>>>
#define TYPELIST_4(T1, T2, T3, T4) Typelist<T1, Typelist<T2, Typelist<T3, Typelist<T4, NullType>>>>
//-------------------------------------------------------------------------------------------
// Typelist 활용
// Holder : 임의 타입의 data 하나 보관..
template<typename T> struct Holder
{
T value;
};
// loki 라이브러리의 GenScatterHierachy => MakeCode
template<typename T, template<typename> class Unit>
class MakeCode : public Unit<T>
{
};
template<template<typename> class Unit>
class MakeCode<NullType, Unit>
{
};
int main()
{
MakeCode<int, Holder> mc1; // 기반 클래스 Holder<int> 이므로
MakeCode<double, Holder> mc2; // Holder<doulbe>
MakeCode<NullType, Holder> mc3; // empty
}