반응형

Policy Based Design

  • 클래스가 사용하는 정책을 템플릿 인자를 통해서 교체 할 수 있게 만드는 디자인

  • 성능 저하 없이 정책을 교체 할 수 있음

  • 대부분의 정책은 담은 "단위 전략 클래스"는 지켜야 하는 규칙이 있음

    • 규칙을 표현하는 코딩 방식은 없음(인터페이스 사용시 가상 함수이므로 약간의 오버헤드 발생), C++20 concept 문법

    • 우리가 만든 동기화 정책클래스는 "lock/unlock" 함수가 필요함

    • 템플릿 기반 라이브러리, 특히 STL에서 널리 사용되는 디자인 기법

#include <iostream>
using namespace std;

// 1개의 클래스로 정책 템플릿 인자를 통해서 다르게 구현 할 수 있도록 디자인
template<typename T, typename ThreadModel> class List
{
    ThreadModel tm;
public:
    void push_front(const T& a)
    {
        tm.lock();
        // 구현부 코드
        tm.unlock();
    }
};

// 싱글 쓰레드 기반 환경용
class NoLock
{
public:
    inline void Lock() {};
    inline void Unlock() {}
};

// 멀티 쓰레드 기반 환경용
class MutexLock
{
public:
    inline void Lock() {};
    inline void Unlock() {}
};

// 환경에 따라서 클래스 생성 시 전략을 선택 할 수 있음
// 싱글 쓰레드용 생성
//List<int, NoLock> s;
// 멀티 쓰레드용 생성
List<int, MutexLock> s;

int main()
{
    s.push_front(10);
}

 

STL allocator

  • C++에서 메모리 할당 방법은 다양함

    • new, malloc, calloc, win32 api, linux system call

  • STL에서는 각 데이터 컨테이너의 템플릿 인자로 메모리 할당 방식에 대한 전략을 선택할 수 있도록 지원함

#include <iostream>
using namespace std;

// STL의 Vector 생각
// 메모리 할당기
template<typename T> class allocator
{
public:
    T* allocate() {}
    void deallocate() {}
};

template<typename T, typename Ax = allocator<T>> class vector
{
    T* buff;
    Ax ax;
public:
    void resize(int n)
    {
        // 버퍼 재할당이 필요하다면 어떻게 할까요?
        // new, malloc, calloc, win32 api, linux system call
        T* p = ax.allocate(n);
        ax.deallocate(p);
    }
};

int main()
{
    vector<int, MyAlloc<int>> v(10);
    v.resize(20);
}

 

rebind

#include <iostream>
using namespace std;

template<typename T> class allocator
{
public:
    T* allocate(int sz) { return new T[sz]; }
    void deallocate(T* p) { delete[] p; }

    template<typename U> struct rebind
    {
        typename allocator<U> other;
    };
};

template<typename T, typename Ax = allocator<T> > class list
{
    struct NODE
    {
        T data;
        NODE* next, * prev;
    };
    //Ax ax; // allocator<int>
    //allocator<int>::rebind<NODE>::other ax; // allocator<NODE> ax;
    typename Ax::template rebind<NODE>::other ax; // allocator<NODE> ax;
public:
    void push_front(const T& a)
    {
        ax.allocate(1);
    }
};

int main()
{
    list<int> s;
    s.push_front(10);
}
반응형

반응형

CRTP 활용한 싱글톤(Singleton) 만들기

  • 싱글톤: 하나의 객체만 생성 할 수 있게 만드는 디자인 패턴
    • private 생성자
    • 복사와 대입 금지
    • 하나의 객체를 만들어서 리턴하는 static 멤버 함수

단일 Singletone 패턴 클래스

#include <iostream>
using namespace std;

class Cursor
{
private:
    Cursor() {}
public:
    Cursor(const Cursor& c) = delete;
    void operator=(const Cursor& c) = delete;

    static Cursor& getInstance()
    {
        static Cursor instance;
        return instance;
    }

};

int main()
{
    Cursor& c1 = Cursor::getInstance();
    Cursor& c2 = Cursor::getInstance();
}

CRTP 패턴 Singleton 패턴 클래스

#include <iostream>
using namespace std;

template<typename T>
class Singleton
{
protected:
    Singleton() {}
public:
    Singleton(const Singleton& c) = delete;
    void operator=(const Singleton& c) = delete;

    static T& getInstance()
    {
        static T instance;
        return instance;
    }
};

class Mouse : public Singleton<Mouse>
{
};

int main()
{
    Mouse& m1 = Mouse::getInstance();
    Mouse& m2 = Mouse::getInstance();
}

 

CRTP 활용한 Unique한 기반 클래스 만들기

  • 기반 클래스의 static memeber data는 모든 파생 클래스에 의해 공유됨

  • 파생 클래스 별로 다른 static member data가 필요한 경우, 서로 다른 기반 클래스를 사용해야 함

  • CRTP를 사용하면 모든 파생 클래스 별로 다른 타입의 기반 클래스를 만들 수 있음

static 멤버 데이터를 관리하는 단일 클래스

#include <iostream>
using namespace std;

class Object
{
public:
    static int cnt;

    Object() { ++cnt; }
    ~Object() { --cnt; }

    static int getCount() { return cnt; }
};
int Object::cnt = 0;

int main()
{
    Object c1, c2;
    cout << c1.getCount() << endl;
}

static 멤버 데이터를 관리하는 유일한 기반 클래스

#include <iostream>
using namespace std;

template<typename T>
class Object
{
public:
    static int cnt;

    Object() { ++cnt; }
    ~Object() { --cnt; }

    static int getCount() { return cnt; }
};
template<typename T> int Object<T>::cnt = 0;

class Mouse : public Object<Mouse>
{

};

class Keyboard : public Object<Keyboard>
{

};

int main()
{
    Mouse m1, m2;
    Keyboard k1, k2;
    cout << m1.getCount() << endl;
    cout << k1.getCount() << endl;
}
반응형

반응형

CRTP(Cruiously Recurring Template Pattern) 활용

  • 기반 클래스에서 파생 클래스의 이름을 사용할 수 있게 하는 기법

  • 파생 클래스를 만들때 기반 클래스의 템플릿 인자로 파생 클래스 이름을 전달

기준 코드

  • 메인 함수 호출 시 기반 클래스의 OnClick 함수가 호출됨
  • 파생 클래스의 OnClick 함수를 호출하기 위해서는?
#include <iostream>
using namespace std;

class Window
{
public:
    void msgLoop()
    {
        OnClick();
    }
    void OnClick() { cout << "Window OnClick" << endl; }
};

class FrameWindow : public Window
{
public:
    void OnClick() { cout << "FrameWindow OnClick" << endl; }
};

int main()
{
    FrameWindow fw;
    fw.msgLoop(); // OUTPUT : Window OnClick
}

 

구현 코드

  • 비 가상 함수(Non virtual function)를 가상 함수 처럼 동작하게 함
  • 파생 클래스를 만들때 기반 클래스의 템플릿 인자로 파생 클래스 이름을 전달
    • this 포인터를 파생 클래스 타입으로 캐스팅 후 함 수 호출
#include <iostream>
using namespace std;

template<typename T>
class Window
{
public:
    void msgLoop()
    {
        // this 포인터를 파생 클래스인 T 타입으로 캐스팅 후 호출    
        static_cast<T*>(this)->OnClick();
    }
    //가상함수로 만들면 가상함수 테이블이 관리됨
    void OnClick() { cout << "Window OnClick" << endl; }
};

class FrameWindow : public Window<FrameWindow>
{
public:
    void OnClick() { cout << "FrameWindow OnClick" << endl; }
};

int main()
{
    FrameWindow fw;
    fw.msgLoop(); // OUTPUT : FrameWindow OnClick
}
반응형

반응형

thin template

  • 템플릿의 단점 중 한가지는 코드 메모리의 증가
  • 모바일등 메모리가 부족한 환경에서 사용할 수 있는 기법

기준 코드

  • 4개의 함수 구성 * 3개의 타입 사용 = 총 12개의 함수 생성

#include <iostream>
using namespace std;

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;
}

T를 사용하지 않는 모든 멤버를 기반클래스에 정의하고 상속받아서 구현

  • 2개의 함수 구성 * 3개의 타입 사용 + 기반 클래스 함수 2개 = 총 8개의 함수 생성

#include <iostream>
using namespace std;

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;
}

모든 멤버를 기반 클래스로 구현

  • 모든 T는 void*로 변경

  • void*를 바로 사용하면 캐스팅이 불편함으로 캐스팅을 책임지는 파생 클래스를 템플릿으로 제공

#include <iostream>
using namespace std;

class VectorBase
{
protected:
    void* buff;
    int sz;
public:
    int size() const {}
    bool empty() const {}
    void push_front(const void* a) {}
    void* front() {}
};

// void*를 바로 사용하면 캐스팅의 불편함이 있음
// 캐스팅을 책임지는 파생 클래스를 템플릿으로 제공
// 캐스팅만 책임지는 함수는 inline로 치환하면 함수화 되지 않음
template<typename T> class Vector : public VectorBase
{
public:
    inline void push_front(const T& a) { VectorBase::push_front(static_cast<void*>(a)) }
    inline T& front() { return static_cast<T&>(VectorBase::front()); }
};

int main()
{
    Vector<int> v1;
    Vector<short> v2;
    Vector<double> v3;
}
  • 기반 클래스 멤버를 직접 호출 방지를 위해 private 상속으로 변경

#include <iostream>
using namespace std;

class VectorBase
{
protected:
    void* buff;
    int sz;
public:
    int size() const {}
    bool empty() const {}
    void push_front(const void* a) {}
    void* front() {}
};

// void*를 바로 사용하면 캐스팅의 불편함이 있음
// 캐스팅을 책임지는 파생 클래스를 템플릿으로 제공
template<typename T> class Vector : private VectorBase
{
public:
    int size() const { return VectorBase::size(); }
    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_cast<T&>(VectorBase::front()); }
};

int main()
{
    Vector<int> v1;
    Vector<short> v2;
    Vector<double> v3;
}
반응형

반응형

가변인자 템플릿(Variadic template)를 활용하여 tuple 전체 요소를 출력

 

#include <iostream>
#include <tuple>
using namespace std;

//튜플과 index_sequence를 받아서 튜플 전체 요소를 출력
template<typename TP, size_t ... I> 
void print_tuple_imp(const TP& tp, const index_sequence<I...>&)
{
    int x[] = { get<I>(tp)... };

    for (auto& n : x)
        cout << n << ", ";
}

template<typename TP>
void print_tuple(const TP& tp)
{
    // 튜플 사이즈를 구해서 index_sequence를 생성하여 print_tuple_imp에 전달
    print_tuple_imp(tp, make_index_sequence<tuple_size<TP>::value>());
}

int main()
{
    tuple<int, int, int> tp(1, 2, 3);

    print_tuple(tp);
}
반응형

반응형

기반 클래스의 멤버에 접근하는 방법(기반 지식)

  • 기반 클래스의 멤버와 파생클래스의 멤버의 이름이 동일할때는 자신(파생 클래스)의 멤버가 우선

  • 기반 클래스의 멤버에 접근하는 방법

    • d.Base::value

    • static_cast<Base&>(d).value;

  • 값 캐스팅과 참조 캐스팅

    • static_cast<Base>(d): 임시객체 생성. lvalue가 될 수 없음

    • static_cast<Base&>(d): 임시객체를 생성 안함. lvalue가 될 수 있음

#include <iostream>
using namespace std;

struct Base
{
    int value = 10;
};

struct Derived : public Base
{
    int value = 20;
};

int main()
{
    Derived d;

    cout << d.value << endl; // 20
    cout << d.Base::value << endl; // 10

    cout << static_cast<Base>(d).value << endl; // 10 임시객체 생성
    cout << static_cast<Base&>(d).value << endl; // 10 참조

    //static_cast<Base>(d).value = 30; // Error
    static_cast<Base&>(d).value = 30; // OK

}

 

Tuple_element

  •  tuple이 가진 N번째 요소의 타입을 구하는 템플릿

  • 메인 템플릿(Main template) 생성, 구현은 불필요함으로 생략

  • 0번째 요소를 구하는 부분 특수화 구현

  • N번째 요소를 구하는 부분 특수화 구현(Recursive)

#include <iostream>
using namespace std;

// tuple 메인 템플릿
template<typename ... Types> struct xtuple
{
    static constexpr int N = 0;
};

// tuple 부분 특수화
template<typename T, typename ... Types> 
struct xtuple<T, Types...> : public xtuple<Types...> // 타입을 상속을 통해 재귀 저장 효과
{
    T value;
    xtuple() = default; // 기본 생성자
    xtuple(const T& v, const Types& ... args) 
        : value(v), xtuple<Types...>(args...) {} // Pack expansion으로 가변인자 전달

    static constexpr int N = xtuple<Types...>::N + 1;
};

// 메인 템플릿
template<size_t N, typename TP> struct xtuple_element;

// 요소의 타입을 구할 수 있도록 부분 특수화
template<typename T, typename... Types> struct xtuple_element<0, xtuple<T, Types...>>
{
    typedef T type;
    typedef xtuple<T, Types...> tupleType;
};

// 요소의 타입을 구할 수 있도록 부분 특수화
template<size_t N, typename T, typename... Types> 
struct xtuple_element<N, xtuple<T, Types...>>
{
    typedef typename xtuple_element<N-1, xtuple<Types...>>::type type;
    typedef typename xtuple_element<N-1, xtuple<Types...>>::tupleType tupleType;
};

// xtuple 요소를 꺼내는 함수
template<size_t N, typename TP>
typename xtuple_element<N, TP>::type& xget(TP& tp)
{
    return static_cast<typename xtuple_element<N, TP>::tupleType&>(tp).value;
}

int main()
{
    xtuple<int, double, char> t3(1, 3.4, 'A'); // 1
    
    cout << xget<1>(t3) << endl; // 3.4
    xget<1>(t3) = 1.1;
    cout << xget<1>(t3) << endl; // 1.1
}
반응형

반응형

tuple C++11 표준

  • 서로 다른 타입의 객체를 N개 보관하는 템플릿
#include <iostream>
#include <tuple>
using namespace std;

int main()
{
    tuple<> t0;
    tuple<int> t1(1);
    tuple<int, double, int, char> t4(1, 3.4, 2, 'A');

    get<2>(t4) = 15; // 참조 리턴으로 값 변경 가능

    cout << get<2>(t4) << endl; // 2
}

tuple 만들기

  • 가변인자 템플릿을 사용해서 메인 템플릿(Main template)을 만듬

  • 1개의 요소를 보관할 수 있도록 부분 특수화 구현

  • 상속을 사용해서 N개를 보관할 수 있게 구현

  • 생성자등 필요한 멤버 추가

#include <iostream>
using namespace std;

// tuple 메인 템플릿
template<typename ... Types> struct xtuple
{
    static constexpr int N = 0;
};

// tuple 부분 특수화
template<typename T, typename ... Types> 
struct xtuple<T, Types...> : public xtuple<Types...> // 타입을 상속을 통해 재귀 저장 효과
{
    T value;
    xtuple() = default; // 기본 생성자
    xtuple(const T& v, const Types& ... args) 
        : value(v), xtuple<Types...>(args...) {} // Pack expansion으로 가변인자 전달

    static constexpr int N = xtuple<Types...>::N + 1;
};

int main()
{
    xtuple<> t0;
    xtuple<int> t1(3);
    xtuple<int, double, char> t4(1, 3.4, 'A');
}
반응형

반응형

함수의 인자 타입을 구하는 traits 만들기

  • 메인 템플릿(Main template)만들고 typedef T type 제공

  • 함수 타입인 T 안에 있는 함수 인자 타입을 얻을 수 있도록 부분 특수화 

    • T -> R(A1, A2), T -> R(A1, Types...)

  • N번째 인자 타입 구하는 방법

    • 0번째 인자의 타입을 구하는 부분 특수화 작성

    • N번째 인자의 타입을 구하는 부분 특수화 작성

      • N == 0이 될때까지 Recursive 사용

      • Recuresive 사용시, 함수의 0번째 인자를 제거하고 N-1 사용

        • argument_type<3, R(A1, A2, A3, A4))::type

          • argument_type<2, R(A1, A2, A3, A4))::type

            • argument_type<1, R(A1, A2, A3, A4))::type

              • argument_type<0, R(A1, A2, A3, A4))::type

#include <iostream>
#include <vector>
#include <type_traits>
using namespace std;

double hoo(short a, int b, double c) { return 0; }

// 메인 템플릿
template<size_t N, typename T> struct argument_type
{
    typedef T type;
};

// 0번째 인자의 타입을 구하는 부분 특수화 작성
template<typename T, typename A1, typename ... Types> 
struct argument_type<0, T(A1, Types ...)>
{
    typedef A1 type;
};

// N번째 인자의 타입을 구하는 부분 특수화 작성
template<size_t N, typename R, typename A1, typename ... Types> 
struct argument_type<N, R(A1, Types ...)>
{
    typedef
        typename argument_type<N - 1, R(Types...)>::type type;
};

template<typename T> void foo(const T& t)
{
    // T : double(short, int, double)
    typename argument_type<0, T>::type ret;
    cout << typeid(ret).name() << endl;
}

int main()
{
    foo(hoo);
}
반응형

+ Recent posts