반응형

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;
}
반응형

반응형

가변인자 템플릿(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);
}
반응형

반응형

type traits 개념

  • 컴파일 타임에 타입에 대한 정보를 얻거나 변형된 타입을 얻을때 사용하는 도구(메타 함수)

  • <type_traits> 헤더로 제공됨(c++11)

type query를 위한 type traits 만드는 방법

  • 메인 템플릿(Primary template)에서 false 반환( value = false )

  • 부분 특수화(Partial specialization)에서 true 반환( value = true )

 

is_pointer 예제

  • 간단히 템플릿 파라미터 T가 포인터 타입 여부 확인 도구

  • 메인 템플릿(Primary template)에서 false 반환( value = false )
  • 포인터 타입용 부분 특수화(Partial specialization)에서 true 반환( value = true )
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T> struct is_pointer_custom
{
    enum { value = false }; 
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T*>
{
    enum { value = true };
};

template<typename T> void foo(T v)
{
    if (is_pointer_custom<T>::value)
        cout << "pointer" << endl;
    else
        cout << "not pointer" << endl;
}

int main()
{
    int n = 3;
    foo(n);
    foo(&n);
}

 

is_pointer 개선 예제

  • c++11 기준 코드 개선
  • 좀더 다양한 포인터 타입 지원(const, volatile, const volatile)
#include <iostream>
#include <type_traits>
using namespace std;

template<typename T> struct is_pointer_custom
{
    //enum { value = false }; 
    static constexpr bool value = false; // c++11
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T*>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* const>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* volatile>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

// 포인터 타입에 대해서 부분 특수화 필요
template<typename T> struct is_pointer_custom<T* const volatile>
{
    //enum { value = true };
    static constexpr bool value = true; // c++
};

int main()
{
    cout << is_pointer_custom<int>::value << endl;
    cout << is_pointer_custom<int*>::value << endl;
    cout << is_pointer_custom<int* const>::value << endl;
    cout << is_pointer_custom<int* volatile>::value << endl;
    cout << is_pointer_custom<int* const volatile>::value << endl;
    cout << is_pointer_custom<int* volatile const>::value << endl;
}
반응형

반응형

클래스 이름::xx 접근 가능한 요소들

  • 값: enum 상수, static 멤버 변수

  • 타입: typedef, using

템플릿 의존적으로 타입의 이름에 접근할때는 typename 키워드를 붙여야한다.

  • T::DWORD -> 컴파일러가 DWORD를 값으로 해석

  • typename T:DWORD -> 컴파일러가 DWORD를 타입으로 해석

템플릿이 아닌 경우에는 typename을 사용할 수 없다.

  • typename T::DWORD* p; // OK

  • typename Test::DWORD* p; // Error

#include <iostream>
using namespace std;

class Test
{
public:
    enum { value1 = 1 };
    static int value2;
    typedef int INT;
    using SHORT = short;
    class innerClass {};
    using DWORD = int;
};
int Test::value2;

template<typename T>
int foo(T t)
{
    // 타입으로 해석
    //typename T::DWORD* p;
    
    // 값으로 해석
    //T::DWORD* p;

    return 0;
}

int main()
{
    Test t;
    foo(t);
}

 

 

반응형

반응형

클래스 템플릿안에 friend 함수를 선언하는 방법

  • friend 함수 선언시에 함수 자체를 템플릿 모양으로 선언

  • friend 관계: N:N

#include <iostream>
using namespace std;

template<typename T>
class Point
{
    T x, y;
public:
    Point(T a = { 0 }, T b = { 0 }) : x(a), y(b) {}
    template<typename U>
    friend ostream& operator<<(ostream& os, const Point<U>& p);
};

template<typename T>
ostream& operator<<(ostream& os, const Point<T>& p)
{
    return os << p.x << ", " << p.y;
}


int main()
{
    Point<int> p(1, 2);
    cout << p << endl;

    Point<double> p2(1.2, 2.3);
    cout << p2 << endl;
}

 

  • friend 함수를 일반 함수로 구현하고 구현부를 클래스 템플릿 내부에 포함

  • friend 관계: 1:1

#include <iostream>
using namespace std;

template<typename T>
class Point
{
    T x, y;
public:
    Point(T a = { 0 }, T b = { 0 }) : x(a), y(b) {}
    friend ostream& operator<<(ostream& os, const Point<T>& p)
    {
        return os << p.x << ", " << p.y;
    }
};


int main()
{
    Point<int> p(1, 2);
    cout << p << endl;

    Point<double> p2(1.2, 2.3);
    cout << p2 << endl;
}
반응형

반응형

배열의 특징

  • 자신과 동일한 타입으로 초기화 될 수 없다.
  • 배열의 이름은 배열의 첫번째 요소의 주소로 암시적 형 변환 된다.
  • 배열을 가리키는 참조를 만들 수 있다.

함수 템플릿을 만들때

  • 배열을 값으로 받으면 T는 요소 타입의 포인터로 결정된다.
  • 배열을 참조로 받으면 T는 배열 타입으로 결정된다.
#include <iostream>

template<typename T>
void foo(T a)
{

}

template<typename T>
void goo(T& a)
{

}

int main()
{
    int x[3] = { 1,2,3 };

    foo(x); 
    goo(x);
}

 

관련 예제

  • 문자열의 타입 : char 배열
  • 문자열을 값으로 받으면 T는 const char* 결정되고, 참조로 받으면  const char[]로 결정된다.
  • 크기가 다른 배열은 다른 타입이다.
#include <iostream>

template<typename T>
void foo(T a, T b)
{

}

template<typename T>
void goo(T& a, T& b)
{

}

int main()
{
    foo("orange", "apple"); // ok
    goo("orange", "apple"); // error
}

 

반응형

반응형
  • Lazy Instantiation
    • 사용되지 않은 템플릿 클래스는 인스턴스화 되지 않음
    • 사용되지 않은 템플릿 클래스의 멤버 함수도 인스턴스화 되지 않음
    • 사용되지 않은 템플릿 클래스의 static 함수도 인스턴스화 되지 않음
    • 사용되지 않은 전역 템플릿 변수도 인스턴스화 되지 않음
template<typename T> class A
{
    T data;
public:
    void foo(T n) { *n = 10; } // 참조 오류 코드
};

int main()
{
    A<int> a;
    // a.foo(1); 템플릿의 멤버 함수가 호출되지 않는다면 인스턴스화 되지 않으므로 컴파일 성공
}
struct Resource1
{
    Resource1() { cout << "Resource1()" << endl; }
    ~Resource1() { cout << "~Resource1()" << endl; }
};

struct Resource2
{
    Resource2() { cout << "Resource2()" << endl; }
    ~Resource2() { cout << "Resource2()" << endl; }
};

template<typename T> struct Test
{
    Resource1 res1; // 클래스내 메모리 할당이 필요하므로 인스턴스화됨
    static Resource2 res2; // static 멤버는 별도 메모리 할당으로 인스턴스화 되지 않음
};
template<typename T> Resource2 Test<T>::res2; // 사용되지 않아서 인스턴스화 되지 않음

int main()
{
    cout << "main" << endl; // 1. 메인 출력
    Test<int> t; // 2. Resource1 생성자 출력
}

 

  • if 문과 Lazy Instantiation
    • if 문은 "실행시간 조건문"으로 컴파일 시간에 조건이 false로 결정되어도 if 문에 있는 코드는 항상 사용되는 것으로 간주함
    • C++17 if constexpr는 "컴파일 시간 조건문"으로 조건이 false로 결정되면 if 문에 포함된 코드는 사용되지 않는 것으로 간주됨
    • 동일한 이름의 함수가 여러개 있을 때 어떤 함수를 호출할 지 결정하는 것은 컴파일 시간에 결정되므로 선택되지 않은 함수가 템플릿이라면 인스턴스화 되지 않음
template<typename T> void foo(T n)
{
    *n = 10; // 참조 오류 코드
}

int main()
{
    if(false) // 런타임 조건식이므로 foo는 인스턴스화 됨
        foo(0);

    if constexpr ( false ) // C++17의 컴파일타임 조건식이므로 foo는 인스턴스화 되지 않음
        foo(0);
}
반응형

반응형
  • 함수와 템플릿
    • square는 함수가 아닌 함수 템플릿, square<int>가 함수
template<typename T> T square(T a)
{
    return a * a;
}

int main()
{
    printf("%p\n", &square); // 함수 템플릿의 주소 출력 error
    printf("%p\n", &square<int>); // int 버전 함수 주소 출력 ok
    printf("%p\n", static_cast<int(*)(int)>(&square)); // int 버전 함수로 캐스팅후 주소 출력 ok

    auto p = &square; // 함수 템플릿 주소는 담을 수 없으므로 error
    auto p = &square<int>; // int 버전 함수 주소 담기 ok
    auto p = static_cast<int(*)(int)>(&square); // int 버전 함수 주소 담기 ok
}

 

  • 템플릿의 구현부는 항상 헤더파일에 함께 제공하여야 함
    • 아래와 같이 함수 템플릿을 헤더와 소스파일로 분리하면 main에서 square 호출 시 실제 Lib.h 헤더파일만 참고하므로 템플릿을 기반으로 실제 int형 함수 생성이 필요한데 생성할 구현부 확인이 불가능하여 에러 발생
// 함수 템플릿을 선언과 구현부를 헤더와 소스파일로 분리한 잘못 구현한 예
////////////////////////////////////////
// Lib.h
int add(int a, int b);
template<typename T> T square(T a);
////////////////////////////////////////

////////////////////////////////////////
// Lib.cpp
int add(int a, int b) 
{
    return a + b;
}

template<typename T> T square(T a)
{
    return a * a;
}
////////////////////////////////////////

////////////////////////////////////////
// main.cpp
#include "Lib.h"
int main()
{
    add(1, 2);
    square(3);
}
////////////////////////////////////////
// 함수 템플릿을 헤더 파일로 모두 제공한 예
////////////////////////////////////////
// Lib.h
int add(int a, int b);
template<typename T> T square(T a)
{
    return a * a;
}
////////////////////////////////////////

////////////////////////////////////////
// Lib.cpp
int add(int a, int b) 
{
    return a + b;
}
////////////////////////////////////////

////////////////////////////////////////
// main.cpp
#include "Lib.h"
int main()
{
    add(1, 2);
    square(3);
}
////////////////////////////////////////
반응형

+ Recent posts