사전적 의미의 delegate는 '대리자'로써 뭔가를 대신 해준다는 의미고, C#에서의 delegate는 일종의 콜백 함수 '리스트'로써 입력과 출력이 동일한 함수들을 일괄 호출하는데 사용된다.


이게 C++ 관점에서 보면 함수 포인터 리스트를 들고 있는 간단한 클래스 정도인데, 개념적으로도 어렵지 않고 구현하는 것도 그렇게 어렵지 않다. 말 그대로 함수 포인터 리스트를 만들어도 되고, 인터페이스 클래스를 정의하고 그걸 상속 받은 클래스 리스트를 만들어도 된다. 다만...필요할때 마다 매번 만드는게 은근 귀찮아 template과 operator overriding을 이용하여 재사용 가능한 delegate 클래스를 만들어 보도록 하겠다(c++ 11 이상).


우선 C# delegate의 인터페이스를 살펴 보면 :

  • 콜백 함수를 등록하기 위한 operator '+='
  • 등록된 콜백 함수를 제거하기 위한 operator '-='
  • 등록된 콜백 함수를 개별로 호출 하기 위한 iterate 인터페이스
정도로 정리가 된다.

이 포스트에서는 전체 코드는 살펴 보지 않고 부분 별 구현 내용만 살짝씩 살펴 볼 예정이다. 전체 코드는 다음 링크를 타고가면 된다(전체 코드 보기 : https://github.com/ChoiIngon/gamnet/blob/master/Gamnet/Library/Delegate.h)

클래스 선언 :
template <class T> class Delegate; /* undefiend */
template <class R, class... ARGS> class Delegate<R(ARGS...)>
template <class... ARGS> class Delegate<void(ARGS...)>
기본적으로 std::function과 비슷한 문법으로 사용하기 위해서 템플릿 특수화를 적용했다. 선언만 하고 정의는 구현되지 않은 기본 클래스를 만들고, 그 후 템플릿 특수화가 적용된 Delegate 클래스를 선언한다. std::function 처럼 사용 할 수 있도록 '리턴타입(인자 리스트)' 와 같은 형태로 선언하는데 여기서 눈여겨 봐야 할 부분은 리턴 타입이 void 형인지 아닌지에 따라 특수화를 나눴다는 것이다. 콜백 함수 리스트를 호출하는 부분에서 리턴 타입에 따라 구현이 달라져야 하기 때문에 클래스 자체를 템플릿 특수화를 통해 구분했다.


함수 포인터 리스트 :

std::list<std::function<R(ARGS...)>> funcs;

일반 함수 포인터 뿐만 아니라 클래스 멤버 함수도 저장 할수 있도록 std::function 리스트를 사용 했다.


operator += : 

Delegate<R(ARGS...)>& operator += (std::function<R(ARGS...)> const& arg)

...자세한 설명은 생략한다...


operator -= :

기존에 등록된 함수 포인터 주소를 비교하여 리스트에서 삭제해주는 오퍼레이터다. std::function::target 템플릿 멤버 함수를 사용하는데 여기서 주의 해야 할 부분이 템플릿 클래스의 템플릿 멤버 함수를 호출하게 되면 컴파일러가 타입을 제대로 추론하지 못해 템플릿 멤버 함수에 대해서 컴파일 오류를 발생 시킨다. 이럴 경우 명시적으로 템플릿 함수라는 것을 지정하기 위해 함수의 호출 부 앞에 'template' 키워드를 붙여 준다.

Delegate<R(ARGS...)>& operator -= (std::function<R(ARGS...)> const& arg)

{

R (*const* ptr)(ARGS...) = arg.template target<R(*)(ARGS...)>();

...

참고 : [진리는어디에] - 'template' 키워드를 한정자로 사용하기(C++)


사용 :

#include <iostream>


int f_1(int a, int b)

{

return a + b;

}


int g_1(int a, int b)

{

return a - b;

}


void f_2(int a, int b)

{

std::cout << a + b << std::endl;

}


void g_2(int a, int b)

{

std::cout << a - b << std::endl;

}


int main()

{

Delegate<int(int, int)> delegate_1;

delegate_1 += f_1;

delegate_1 += g_1

std::cout << delegate_1(100, 10) << std::endl; // print 90 only


for(auto itr=delegate_1.begin(); itr != delegate_1.end(); itr++)

{

std::cout << (*itr)(100, 10) << std::endl; // print 110 and 90

}


Delegate<void(int, int)> delegate_2;

delegate_2 += f_2;

delegate_2 += g_2

delegate_1(100, 10); // print 110 and 90


return 0;

}


기본적으로 위와 같이 구현 되어 있고 설명에 언급 되지 않은 것은 리턴 타입이 void인 경우를 위한 클래스를 하나 더 만든 부분이다.


전체 코드 보기 : https://github.com/ChoiIngon/gamnet/blob/master/Gamnet/Library/Delegate.h


Posted by kukuta

댓글을 달아 주세요

  1. Favicon of https://tood-re.tistory.com BlogIcon 먹튀 검증 2018.08.02 14:04 신고  댓글주소  수정/삭제  댓글쓰기

    잘보고갑니다~

템플릿 멤버 함수를 특별하게 구분해야 할 필요가 있는 경우 'template' 키워드를 사용한다. 아래 예제 코드를 보고 언제 'template' 키워드를 한정자로 사용해야 하는지 살펴 보도록 하자 :


class A

{

public :

template<class T> T function_m() {};

};


template<class U> void function_n(U argument)

{

char object_x = argument.function_m<char>();

}


위 예제에서, 컴파일러는 템플릿 인자 U가 클래스 A의 인스턴스라는 것을 추론하지 못하기 때문에 'function_m'이 템플릿 멤버 함수라는 것을 알지 못하고 '<'를 비교연산자로 처리 버려 컴파일 오류가 발생한다. 컴파일러에게 이것이 템플릿 함수를 호출하는 것이라는 것을 알리기 위해선 아래와 같이 'template' 한정자를 함수 호출부 앞에 추가 해주어야 한다 :

char objec_x = argument.template function_m<char>();


만일 템플릿 특수화 멤버 함수의 이름이 ., -> 또는 :: 연산자 뒤에 나온다면 , 그리고 그 이름이 명시적으로 한정된 템플릿 인자라면, 'template' 키워드를 꼭 앞에 붙여 줘야 한다. 다음 예제를 살펴 보자 :

#include <iostream>

using namespace std;


class X

{

public :

template<int j> struct S

{

vodi h()

{

cout << "member template's member function:" << j << endl;

}

};


template<int i> void f()

{

cout << "primary: " << i << endl;

}

};


template<> void X::f<20>()

{

cout << "specialize, not-type argument = 20" << endl;

}


template<class T> void g(T* p)

{

p->template f<100>();

p->template f<20>();

typename T::template S<40> s; //멤버 템플릿 앞에 스코프 오퍼레이터를 사용한다

s.h();

}


int main()

{

X temp;

g(&temp);

}


위 예제의 결과물을 보자 :

primary: 100

specialized, non-type argument = 20

member template's member function: 40


위에서 'template' 키워드를 사용하지 않았다면 컴파일러는 '<'를 비교 연산자로 해석한다. 아래는 오류를 발생 시키는 예다 :

p->f<100>();

컴파일러는 템플릿 멤버 함수 f를 일반 함수로 해석하고 '<'를 비교 연산자로 해석하여  오류를 발생 시킨다.


결론 :

  • 템플릿 형태로 넘겨 받은 인자의 템플릿 클래스 인스턴스의 템플릿 함수를 호출 할때는 'template' 키워드를 한정자로 붙여야 한다.

  • 템플릿 멤버 함수를 썼는데 오류가 발생하면 일단 'template'을 앞에 붙여 보자.


원문 : 

https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.3.0/com.ibm.zos.v2r3.cbclx01/keyword_template_qualifier.htm

Posted by kukuta
TAG C++, template

댓글을 달아 주세요

갯수가 정해지지 않은 N개의 인자를 사용 할 수 있는 기능으로써 D언어와 C++ 11에서 지원하고 있다

(from. http://en.wikipedia.org/wiki/Variadic_template)


C++ 11 이전 버젼의 템플릿에서는 지정된 갯수만큼의 인자만을 받을 수 있었으나 C++11에서 부터는 임의의 갯수를 받을 수 있도록 변경 되었다. 기본적인 문법은 아래와 같다 :


template<typename... Values> class tuple;


위의 템플릿 클래스 tuple은 어떠한 타입이든 몇개든 상관 없이 템플릿 인자 생성이 가능 하다. 예를 들어서 위 클래스의 인스턴스로 아래와 같은 것이 가능하다 :


tuple<int, std::vector<int>, std::map<std::string, std::vector<int>>> some_instance_name;


물론 0개의 템플릿 인자로도 인스턴스 생성이 가능하고 tuple<>some_instance_name 과 같은 코드도 정상 동작한다. 만일 최소한 하나의 인자는 받아야 한다라고 강제하고 싶다면 아래와 같은 코드도 가능 하다 :


template<typename First, typename... Rest> class tuple;


위 코드에서는 tuple<> some_instance_name 과 같은 코드는 컴파일 에러를 발생 시킨다.


Variadic template은 템플릿 특화(Template specialize)와 결합해서 사용하면 가변인자를 이용한 함수(va_list) 와 같은 기능을 하지만 %c, %d와 같은 귀찮은 예약 인자와 순서를 맞춰야만 하는 귀찮음을 제거 할 수 있는 강력한 도구가 된다. 아래 코드는 임의의  타입으로 구성된 임의의 갯수의 인자를 받아 결합하여 문자열로 리턴 하는 함수를 variadic template을 이용하여 구현 한것이다 :

#include <string>

#include <sstream>

#include <iostream>

template <class T>

void Concat(std::stringstream& stream, const T& t) // 종료 템플릿 함수

{

        stream << t;

}


template <class T, class... ARGS>

void Concat(std::stringstream& stream, const T& t, ARGS&&... args) // 재귀 템플릿 함수

{

        stream << t;

        Concat(stream, args...);

}


template <class... ARGS>

std::string Concat(ARGS... args) // 유저 호출 함수

{

        std::stringstream stream;

        Concat(stream, args...); // 템플릿 특화로 인해 재귀 템플릿 함수가 호출 된다.

        return stream.str();

}


int main()

{

        int n100 = 100;

        int n200 = 200;

        float f100 = 100.0f;

        float f200 = 200.0f;

        std::string str = "string out";

        std::cout << Concat("string ", n100, " ", str, " ", n200, " ", f100, " ", f200 ) << std::endl;

           printf("string %d %s %d %f %f", n100, str.c_str(), n200, f100, f200);

        return 0;

}


ref :

 - http://en.wikipedia.org/wiki/Variadic_template

Posted by kukuta

댓글을 달아 주세요

" error: too few template-parameter-lists"는 신규 버젼 gnu cpp 컴파일러에서 발생하는 에러다.
이 문제는 템플릿 클래스의 static 멤버 변수를 초기화 할 때 발생하며, 해결을 template<>을 static 멤버 변수 초기화 코드 앞에 붙여 주어야 한다.

예를 들어 :


template <class T>

 class A
 {
    static int a;
    static const char * const name;
 };

와 같은 코드가 있다고 해보자. 예전에는 아래와 같이 써도 무방했다 :
 int A::a = 0;
const char * const A::name = NULL;
하지만 위와 같은 코드는 
CeePlusPlus 표준에 의해 이제는 더 이상 유효한 코드가 아니며 "" error: too few template-parameter-lists" 에러를 발생 한다.

아래와 같이 수정하도록 한다 :
 template<> int A<int>::a = 0;
 template<> const char * const  A<int>::name = "example";


Example code 2

 #include <iostream>
 template <int I>
 class B
 {
 public:
   static int b;
 };

template<> int B<1>::b = 1; template <int I> int B<I>::b = 0;

int main() { std::cout << B<1>::b << std::endl; // 1 std::cout << B<2>::b << std::endl; // 0 return 0;
} 
ref.
Too Few Template Parameter Lists : http://www.c2.com/cgi/wiki?TooFewTemplateParameterLists
Posted by kukuta

댓글을 달아 주세요

에러 객체란?

" 에러객체 = 에러코드 + 에러문자열 "

프로그래밍을 하다보면 종종 에러코드를 정의해서 써야하는 경우가 있다. 종종이라기보다는 항상 에러코드를 정의해야한다. C++을 사용하는 나의 경우에는 enum을 주로 이용하여 에러코드가 겹치는 것을 방지하고, 다른 언어를 사용하는 사람들도 각자의 언어에서 제공하는 여러가지 방법을 사용하여 에러 코드를 정의해 쓸 것이다. 이렇게 에러코드들을 주루룩 정의해서 쓰다보면 한가지 아쉬운 점이 꼭 생각난다.

"에러 코드만 넣으면 무슨 에러인지 알수 있는 방법은 없을까?"

오늘 소개해볼 '에러 객체'라는 것은 위의 불편함을 조금이라도 해결해 보기 위해 만들어진 몸부림 중에 하나다.

에러 객체에게 필요한 것들?

중복은 절대 안되!!
에러 코드는 중복되어서는 안된다. 한 가지의 에러를 나타내는데 하나의 코드만 쓰여야하고, 하나의 코드는 하나의 에러만을 가리켜야한다. 그래서 사람들은 enum 같은 곳에 에러 코드들을 정의함으로써 컴파일 타임에 자동적으로 중복되는 에러 코드를 피하려한다. 우리가 필요한 에러 객체에 중복된 코드가 있는 경우엔 컴파일 타임 에러가 발생할 수 있게 해야 한다.

코드만으로 무슨 에러인지 알고 싶어요!!
MS의 에러 코드를 생각 해 보라. 당최 알 수 없는 에러 코드가 떨어진다. 그 코드가 무엇인지 알기위해서는 MSDN을 뒤져 보거나 Error Lookup 툴을 이용하여야만 한다. 물론, 범용성을 제공하기 위해서는 정수형태의 에러코드 말고 다른 형태의 뭔가를 지원한다는 것이 상당히 어렵다. 하지만 나는 내 프로젝트에서 사용할 코드들을 정의하는 것이다. 문자열이 한글이든, 영어든 한가지만 선택하면 되는 것이고, 내가 알 수 있는 내용을 써 넣으면 된다.
지금 나에게 필요한 것은 범용성 보다는 별다른 툴 없이 바로 에러 문자열을 출력할 수 있는 방법이다.

에러 코드와 에러 문자열을 한자리에서 정의 하고 싶어요!!
만일 enum으로 에러 코드를 정의하고 문자열 배열로 에러 문자열들을 정의 하는 것도 괜찮은 방법이라 생각한다. 다만 에러 정의 들이 늘어나면 늘어 날 수록 유지 보수가 힘들어진다(진짜다 직접 해봐라!! 500개가 넘는 문자열 배열 과 에러코드를 동시에 관리하면 머리에 쥐난다).

♥ 어떻게 생겼을까?
위의 요구사항을 토대로 만들어진 코드를 살펴 보자. 장황한 서두와는 달리 실상 코드는 너무나도 간단하게 만들 수 있다.

에러 객체 기본 :
enum XXX_ERRORTYPE
{
     XXX_ERRORTYPE_SUCCESS, 
     XXX_ERRORTYPE_INFO,  
     XXX_ERRORTYPE_WARN,  
     XXX_ERRORTYPE_ERROR  
};

template <int TYPE, int SERVER, int ERRNO> 
struct XXX_ERROR {
     enum {
          ErrorCode = ((TYPE << 8 * 3) | (SERVER << 8 * 2) | ERRNO)
     };
     static const char* Desc;
};

#define XXX_MAKE_ERRORCODE(name, type, server, errno, description) \
const char* XXX_ERROR<type, server, errno>::Desc = #description; \
typedef XXX_ERROR<type, server, errno> name;

// example
XXX_MAKE_ERRORCODE(XXX_ERROR_SOMEERROR,   TZ_ERRORTYPE_ERROR, 1,  1, "XXX 에러.")

그냥 ERROR이라고 하면 다른 뭔가와 충돌이 날것 같아 앞에 XXX를 붙였다. XXX에 큰 의미를 두지는 말자.

먼저 XXX_ERROR 객체를 살펴 보자.
ErrorCode라는 enum을 선언하고 있다. ErrorCode는 값이 아닌 하나의 타입으로 인식되어 컴파일 타임에 결정 된다. TYPE, SERVER, ERRNO 같은게 나오고 이리 저리 시프트가 있어서 복잡해 보이기는 하지만 그것은 개인적으로 에러 코드를 카테고라이징하기 위해 사용하는 것이지 특별한 의미가 있는 것은 아니다. 필요 없다면 ERRNO 하나만으로 써도 무방하다.

Desc는 description의 약자로 static const* 타입으로 선언되어 있다. 꼴에 문자열인지라 아무래도 컴파일 타임에 뭔가를 할 수 없어 대신 런타임에 아무도 내용을 바꾸지 못하도록 const를 사용했다.

XXX_MAKE_ERRORCODE 매크로로 눈을 돌려 보자. 이 매크로는 XXX_ERROR 템플릿 클래스를 찍어내는데 사용된다. 내부에는 static 변수의 초기화와 typedef 외에 다른 일은 하지 않는다. 하지만 한가지 기억할 것이 있다.

"템플릿 클래스는 인자가 다른 경우 다른 타입으로 여겨진다."

위에서 type, server, errno에 다른 경우 동일한 템플릿 클래스로 만들고 있지만 항상 다른 타입으로 여겨진다는 것이다. 만일 위의 세 인자가 같은 경우(혹 하나로 해도 상관은 없다)라면 동일 타입의 static 변수를 두번 초기화하려 한다고 컴파일 에러가 발생한다. 만일 name이 같은 경우 역시 typedef를 두번 하려한다고 컴파일 에러를 발생 시킨다. 어쨋든 중복된 선언을 하게 된다면 컴파일이 안되게 만들었다는 말이다.

사용은 어떻게?
에러 객체라고 하지만 실상 아무런 객체도 생성되지 않는다. 그냥 타입의 enum 상수와 static 변수에 접근하는 것 뿐이다 :

if(XXX_ERROR_SOMEERROR::ErrorCode == SomeFunction())
{
     std::cout << XXX_ERROR_SOMEERROR::Desc << std::endl;
}

위에서 사용된 SomeFunction()은 에러 객체 템플릿 클래스에 대한 어떠한 정보도 없어도 된다. 단순히 정수형태의 에러 코드를 리턴하는 것이면 된다. 리턴하는 것은 에러 객체가 아닌 에러 객체에 정의된 enum을 리턴 한다.

문자열의 출력 역시, 에러 객체에 선언된 static 변수에 접근하여 초기화된 문자열을 출력하는 간단한 형태다.

위와 같은 방식으로 에러 정보를 관리하게 되면 에러 코드 넘버와 에러문자열을 항상 한쌍으로 관리 할 수 있으므로 관리에 용이성과, 컴파일 타임에 중복 체크를 보장 받을 수 있다.

다만 단점으로는 static 변수들의 무수한 생성과 enum 처럼 자동으로 에러 코드를 증가 시켜주는 도구가 없다는 불편함이 있다.



참조 :
 - Modern C++ Design - 안드레 알렉산드레쿠스
 - [진리는어디에] - 템플릿 특화를 이용한 튜플 클래스
 - [진리는어디에] - 템플릿 특화(Template specialize)
 - [진리는어디에] - 템플릿 특화를 이용한 멀티 키 맵(Multi Key Map)
Posted by kukuta

댓글을 달아 주세요

이전에 템플릿 특화(template specialization)특화를 이용한 튜플(Tuple)클래스를 만들어 본적이 있다. 오늘은 비슷한 원리를 이용해 N개의 키를 가질 수 있는 멀티 키 맵을 만들어 보겠다.


멀티키 맵이라고 해서 특별이 다를 것은 없다. 다만 단일 키만을 제공하는 std::map을 좀 더 확장하여 N개의 키를 타입 리스트를 통해 넘겨주는 것 뿐이다. 물론 이런 방법 말고도 구조체를 키로 쓰는 std::map을 활용하거나, 바이너리 메모리로 모든 키들을 복사해서 쓰는등 구현에는 다향한 취향과 트레이드 오프가 존재한다. 오늘 이 포스트에서 소개하는 것은 그 방법들 중 한 가지 방법일 뿐이다.

멀티키 맵에 관련된 설명에 앞서 이전에 다뤘던 Typelist라던지 템플릿 특화의 개념은 이미 이전 포스트에서 익혔다고 가정하고 간단하게 설명하거나 다루지 않고 넘어 가도록 하겠다.

1. 기본 개념은 '상속' - 상속을 통한 코드의 자동 생성

이전의 튜플(Tuple)클래스와 마찬가지로 멀티키 맵도 기본 뼈대는 상속을 통해 만들어 진다. 멀티키 맵의 기본 구조를 보자면 각 키 마다 하위 계층 맵을가지고 있고, 가장 하위 키는 따로 맵을 가지지 않고 바로 값을 가지고 있는 형태다. 한 마디로 계층 구조를 가지는 std::map들을 템플릿을 이용해 자동으로 생성하는 방식이다.

1 : template <class T, class U, class Value>
2 : struct MultiKeyMap<Typelist<T, U>, Value> : private MultiKeyMap<U, Value>
3 : {
4 :    typedef Typelist<T, U> TYPELIST;
5 :    typedef std::map<T, MultiKeyMap<U, Value> > CONTAINER;
6 :    CONTAINER m_mapContainer;
...     .....

위 코드 라인 2번을 보면 여러 개의 키중 한개 키만을 떼어 자식 클래스 내부 std::map의 키 값으로 쓰고 나머지 타입 리스트는 부모 클래스로 올려 버린다. Value는 최상위 클래스의 값으로 저장되어야 하므로 모든 계층구조를 통해 끝까지 올려 보낸다.

위와 같은 상속을 통해 각 키에 해당하는 std::map을 자동으로 생성할 수 있다(이렇게 하지 않았다면 직접 각 계층 구조 마다 std::map을 생성 해 줘야 하는 불편함이 있었을 것이다.)

코드 라인 5번은 멀티키 맵 내부에 값을 저장 할 std::map을 생성한다. 위 코드를 보면 어떻게 계층 구조를 이루는 맵을 생성하는지 알 수 있다.

2. 멀티 키 맵에서의 데이터 접근
데이터의 접근은 위의 개념보다 좀 더 쉽다. 단순히 std::map의 계층 구조를 따라 다 맨 마지막 실제 값에 다다르면 값을 리턴하는 방식이다.

template <class Value>
struct MultiKeyMap<NullType, Value>
{
    Value m_value;
    Value& get(NullType key)
    {
        return m_value;
    }
};

template <class T, class U, class Value>
struct MultiKeyMap<Typelist<T, U>, Value> : private MultiKeyMap<U, Value>
{
    ....
    template <class PList>
    Value& get(PList key)
    {
         MultiKeyMap<U, Value>& mkm = m_mapContainer[key.first];
         return mkm.get(key.second);
    }
    ....
};


3. 멀티 키 맵의 사용
int main() {
    MultiKeyMap<TYPELIST_3(std::string, std::string, int), int> multiKeyMap;
  
    multiKeyMap[PARAMLIST_3("key1", "key2", 0)] = 1000;;
    std::cout << multiKeyMap[PARAMLIST_3("key1", "key2", 0)] << std::endl;
    multiKeyMap.erase(PARAMLIST_3("key1", "key2", 0));

    return 0;
}

멀티 키 맵의 템플릿 인자를 다양화 하기 위해서 Modern C++ Design에서 소개 되고 있는 타입리스트(type list) 를 이용한다. 그리고 타입 리스트에 따라 실제 인자도 다양화하기 위해 PARAMLIST 를 사용하고 있다. 타입리스트와 파라메터 리스트의 개념은 특화를 이용한 튜플(Tuple)클래스 를 참고하면 되겠다.

나머지 상세 코드는 첨부 파일을 참조 바란다.
Posted by kukuta

댓글을 달아 주세요

템플릿의 세계에 빠져 살던 요즘..

동일한 코드가 어디서는 정상적으로 돌아가고, 어디서는 이상동작을 한다거나, 컴파일이 안 되는 경우가 있는데요..
찾아보니 Visual studio의 버그로 인한 것도 있더군요. 7.0에서는 패치되었다고는 하지만…6.0도 분명히 돈 받고 팔던 것인데…오류 사항이 있으면 패치를 할 생각을 안하고 다음 버전에서 때우다니 은근히 맘에 들지 않는군요.

http://womble.decadentplace.org.uk/c++/template-faq.html <- 보다 많은 FAQ가 있습니다. 보시면 많은 도움이 될 듯..그리고 아래는 Visual studio 관련 내용만 간단히 요약 해 두었습니다.

4. Q: 왜 Visual C++ 6.0은 클래스 밖에 선언된 템플릿 멤버 함수 정의를 이해하지 못 할까요?

class my_class {
public:
    template<typename T> void func();
};

template<typename T>
void my_class::func()
{
    // implementation
}

A: 버그 입니다. 7.0에서 수정 되었습니다.

5. Q. 왜 Visual C++ 6.0에서 모든 템플릿 함수들은 똑같은 결과를 리턴 할까요?

template<typename T>
std::size_t my_sizeof()
{
    return sizeof(T);
}

A: 버그 입니다. 7.0에서 수정 되었습니다. 6.0은 템플릿 함수 객체를 템플릿 인자가 아닌, 파라메터 타입에 의해서만 구분합니다. 우회적인 해결 방법으로는 사용하지 않는 템플릿 타입의 함수 파라메터를 기본 인자로 넘겨 주어 각 템플릿 함수 객체를 구분 할 수 있습니다.


template<typename T>
std::size_t my_sizeof(T * = 0)
{
    return sizeof(T);
}
Posted by kukuta

댓글을 달아 주세요

'Modern C++ Design'에서 소개되고 있는 튜플(Tuple)이라는 개념은 일종의 레코드로써 우리가 일반적으로 생각하는 구조체라고 생각하면 된다. 단 구조체는 만들 때 마다 멤버 변수의 이름이나 구조체의 이름을 지정해 줘야 하지만 '그것것에 대해 신경 쓸 필요가 없는.. 다시 말하면 '이름없는 구조체'를 튜플이라고 하고 있다.

어디에 쓰는지야 각자 프로그램을 만들면서 살다 보면 스스로 알게 될 것이고, 이번 포스트에서는 템플릿 특화(Template specialize) 를 이용한 튜플 클래스 구현에 대해서 알아 보겠다.


Tuple 클래스의 기본적인 개념은 '상속'이다.

클래스 Derived가 클래스 Base를 public으로 상속 한다고 생각해 보자. 이런 경우 Derived는 Base의 모든 멤버 변수를 상속 받을 수 있고 또한 접근 할 수 있다. 단, 하위 클래스에서 상위 클래스와 동일한 멤버 변수 혹은 함수를 사용한다면 하위 클래스에 의해 가리워지기 때문에 명시적으로 스코프를 지정하여 특정 멤버 변수에 접근해야 한다는 것이 유일하게 신경 써줘야 할 점이다.

Tuple 클래스는 위에서 설명한 상속의 개념과 멤버 변수 접근 방법을 기반으로 만들어 졌다. 템플릿 인자로 넘어오는 타입 리스트를 이용하여, 각 자료형을 담고 있는 클래스 코드를 생성하고 자료형의 나열 순서에 따라 상속 관계를 자동으로 지정해 줌으로써 최종적으로 모든 멤버 변수를 다 담고 있는 클래스 코드를 생성 해 낸다.

예를 들어 int, std::string, double 형의 타입 리스트가 주어진다고 하면 튜플 클래스는 :

class A {
    double m_value;
};

class B : public A {
    std::string m_value;
};

class C : pubilc B {
    int m_value;
};

와 같은 코드를 자동으로 생성해 준다.

이제 기본 개념은 익혔으니 코드를 살펴 보면서 Tuple 클래스가 어떻게 동작하는지 좀 더 자세히 살펴 보기로 하자.코드에서 사용되고 있는 NullType, Typelist 같은 부분은 'Modern C++ Design'에서 소개 되고 있는 내용을 그대로 가져 왔다(..라고 하지만 지금 쓰고 있는 내용 전부가 그 책에서 다루고 있는 것임..).

template <class TList>
struct Tuple;

template <>
struct Tuple <NullType> {
    Tuple() {};
    Tuple(NullType) {};
};

template <class T, class U>
struct Tuple <Typelist<T, U> > : public Tuple <U>
{
     // ... 다른 코드들
     T m_value;
};

위의 코드는 템플릿 특화를 이용하여 앞에서 설명한 클래스 사속의 자동화를 만들고 있다.

Tuple 클래스를 살펴 보면 기본형 템플릿 클래스를 선언하고, Typelist에 대한 처리와 NullType에 대한 처리를 하는  두개의 특화된 템플릿 클래스를 추가 했음을 알 수 있다. Typelist와 NullType에 관련된 사항은 Modern C++ Design 혹은 포스트 가장 밑에 Reference 항목 혹은 예제로 올려진 코드를 통해 확인하도록 하자.

우리가 TYPELIST_n 매크로를 이용하여 Tuple 클래스의 템플릿을 선언하게 되면, Typelist의 첫번째 인자로 자신의 로컬 영역에 변수를 선언하고, 나머지 인자를 이용해 새로운 부모 Tuple클래스를 만든다. 이런 일들은 컴파일 타임에 재귀적으로 일어나게 되며 Typelist NullType인자만 남아 또다른 특화된 클래스를 만나기 전까지 계속 된다.

위의 과정이 끝나면(NullType의 처리가 끝나면) 필요로 하는 모든 변수를 담고 있는 최종적인 클래스가 생성되고, 그 클래스 상속 트리의 깊이는 Typelist의 길이와 동일하다.

필요한 타입을 모두 담고 있는 클래스를 생성하는데는 성공 하였지만 이제 특정 변수에 접근하는 것이 문제다. 우리가 생성한 Tuple<XXX> 클래스들은 재귀적으로 생성되어 동일한 변수 이름을 사용한다. 이렇게 되면 우리가 사용하게 되는 최종적인 클래스에서는 단 하나의 변수 밖에 볼 수가 없게 된다.

이러한 동일한 이름을 가진 변수들에 접근하기 위해서는 변수 이름 앞에 스코프를 명시적으로 선언하여야만 해당 변수에 접근이 가능해 진다. 예를 들어 위의 class A, B, C 예로 다시 돌아 가서 A에서 선언한 m_value에 접근 하기 위해서는 :
class C : public B {...};
C obj;

obj.C::B::A::m_value;
obj.C::B::m_value;
와 같은 방법으로 변수에 접근해야 한다.

위와 같은 접근을 가능하게 해 주는 것이 Tuple 클래스 내의 Value() 멤버 함수다. 최하위 Tuple클래스는 컴파일 타임에 호출되는 Value() 함수와 템플릿 인자로 넘어오는 상수를 이용하여, 각 상수마다 다른 스코프의 변수에 접근하는 Value() 함수를 만들어 준다.
template <class T, class U>
struct Tuple <Typelist<T, U> > : public Tuple <U>
     // ...다른 코드들
     typedef Typelist<T, U> TYPELIST; // 그냥 편하게 써보려고..-_-;;

     template <unsigned int i>
     typename At<TYPELIST, i>::Result& // 리턴 값. Typelist의 i번째 타입 반환(?)
     Value()
     {
          typedef typename AtScope<TYPELIST, i>::Rsult Result; // 스코프 지정. i번째 부터의 타입 반환
          return Tuple<Result>::m_value;
     }
     // ...다른 코드들...
};

Value가 리턴하는 값을 알아 내기 위해 At 템플릿 클래스와 명시해야 할 스코프를 알아내기 위해 AtScope 템플릿 클래스가 사용되어 진다. 둘 다 재귀적으로 Typelist를 탐색하면서 인자로 주어진 상수가 0이되는 데이터 타입과 스코프를 리턴한다. 코드는 아래와 같다 :

template <class TList, unsigned int i> struct AtScope; // 클래스 원형

template <class T, class U>
struct AtScope<Typelist<T, U>, 0> // 특화된 구현 0 이면 더 이상의 재귀 호출을 하지 않는다
{
    typedef Typelist<T, U> Result;
};

template <class T, class U, unsigned int i>
struct AtScope<Typelist<T, U>, i> // 0이 아닌 상수일 경우 -1하여 계속 재귀적으로 코드 생성
{
    typedef typename AtScope<U, i-1>::Result Result;
};

template <class TList, unsigned int> struct At;

template <class T, class U>
struct At<Typelist<T, U>, 0>
{
    typedef T Result;
};

template <class T, class U, unsigned int i>
struct At<Typelist<T, U>, i>
{
    typedef typename At<U, i-1>::Result Result;
};

여기까지 하면 기본적인 Tuple의 기능은 완료된다. 하지만 매번 사용할 때 마다 각 멤버 변수에 접근하여 값을 일일이 집어 넣어주는 것은 여간 귀찮은 일이 아니다. 그래서 이번에는 생성자를 추가 해 보도록 하자.

생성자는 PARAMLIST_n의 인자를 받아 들인다. 기본적인 개념은 Typelist와 비슷하나 이번에는 데이터 타입이 아닌 변수의 값을 저장한다. 예를 들면 :
#define PARAMLIST_1(P1) std::make_pair(P1, NullType())
#define PARAMLIST_2(P1, P2) std::make_pair(P1, PARAMLIST_1(P2))
#define PARAMLIST_3(P1, P2, P3) std::make_pair(P1, PARAMLIST_2(P2, P3))
... 계속 필요한 만큼 만들것...
와 같이 작성 될 수 있다.

Tuple 클래스의 생성자는 PARAMLIST를 인자로 받아 가장 첫번째 인자는 자신의 로컬 변수를 초기화 하는데 사용하고, 나머지 인자를 상위 클래스의 초기화로 넘긴다.
template <class T, class U>
struct Tuple <Typelist<T, U> > : public Tuple <U> {
     // ...다른 코드들...

     Tuple() {};  // 기본 생성자
     template <class PList> Tuple(PList plist); // 파라메터 리스트를 인자로 받는 생성자

     // ...다른 코드들...
};

template <class T, class U> // 클래스의 템플릿 인자
template <class PList> // 멤버 함수의 템플릿 인자
Tuple<Typelist<T, U> >::Tuple(PList plist) : m_value(plist.first), Tuple<U>(plist.second) {}

이렇게 PARAMLIST의 가장 앞에 있는 변수를 이용해 초기화하는 코드는 NullType 변수를 만날때 까지 계속되며, NullType를 만나게 되면 아무런 초기화 작업도 하지 않고 재귀적 초기화 과정은 종료 종료된다.

지금 까지 작성 해온 Tuple 클래스의 사용 예제 코드를 보면 아래와 같다(전체 코드를 보고 싶다면 첨부 파일을 받아 보도록 하자). :

int main()
{
     typedef Tuple<TYPELIST_3(int, std::string, double)> THREE;
     THREE three(PARAMLIST_3(1, "2", 3.0f));
     std::cout << three.Value<0>() << std::endl;
     std::cout << three.Value<1>() << std::endl;
     std::cout << three.Value<2>() << std::endl;

     typedef Tuple<TYPELIST_1(std::string)> VarToken;
     VarToken varToken(PARAMLIST_1("int"));
     std::cout << varToken.Value<0>() << std::endl;
     return 0;
}

위와 같이 새로운 구조체 생성의 필요 없이 간단한 typedef 문하나만으로 다양한 타입을 가지는 Tuple클래스를 생성할 수 있다. 단점이라면 클래스의 각 변수를 이름이 아닌 숫자로 접근해야 한다는 단점이 있어, 멤버 변수가 많다거나 방대한 양의 코드때문에 순서를 제대로 기억하지 못할 때는 정상적으로 사용하기가 까다롭다.

개인적으로 Tuple 클래스가 가장 적합하게 사용 될 때는 딱히 구조체를 만들기는 뭐하고, 데이터는 넘겨야 하겠고, 별다른 부담 없는 자료형이 없을까 할 때 사용 한다거나, union으로 만들기도 그렇고, void* 로 만들기 뭐할 때 사용 하면 부담 없이 쓰고 버릴 수 있는 자료형으로 사용하면 좋을 듯하다.

이상 포스트를 마친다..(간만에 쓰니..힘들다...헥헥..)

Reference :
 * NullType에 대한 설명 : http://ikpil.tistory.com/1035
 * Typelist에 대한 설명 : http://ikpil.tistory.com/search/Typelist
 * [진리는어디에] - 템플릿 특화(Template specialize)
 * boost Tuple Library(와방 좋음) : http://www.boost.org/doc/libs/1_42_0/libs/tuple/doc/tuple_users_guide.html

'진리는어디에' 카테고리의 다른 글

템플릿(template) FAQ - Visual Studio 6.0 Bugs  (0) 2010.03.30
ASN.1  (0) 2010.03.22
템플릿 특화를 이용한 튜플 클래스  (0) 2010.02.15
템플릿 특화(Template specialize)  (2) 2010.02.03
C++ #line은 뭐지?  (0) 2010.02.02
WideCharToMultiByte()  (0) 2009.03.20
Posted by kukuta

댓글을 달아 주세요

'템플릿 특화'라는 것은 템플릿 파라메터에 특정 데이터 타입을 지정하므로써 해당 템플릿 클래스로 가능한 구체화 중에서 특정 데이터 타입의 경우만을 특정 코드로 대응 시키는 것을 말합니다.

예를 들자면..

template <class Window, class Contoller>
class Widget {
    ...일반화된 구현...
};

라는 클래스가 있다면 다음과 같이

template <>
class Widget <ModalDialog, MyController> {
    ...ModalDialog, MyController 에만 특화된 구현...
};

특화 될 수 있습니다(예제는 Modern C++ Design에서 베껴 왔습니다. ModalDialog, MyController는 구현되어 있다고 가정 합시다.).

만일 프로그래머가 Widget<ModalDialog, MyController> specialWidget 과 같은 코드 작성했다고 하면, 특정 Widget 클래스의 구현중에서 ModalDialog와 MyController에만 특화된 클래스 코드를 찾아 객체를 생성하게 됩니다. 어떻게 찾는지는 컴파일러가 너무나도 똑똑하게 알아서 잘 해주므로 굳이 신경 쓸 일은 없습니다.

반면, Widget<BaseWindow, BaseController> baseWidget 과 같이 미리 지정되지 않은 타입으로 Widget 클래스의 객체를 만들게 되면 일반화된 Widget 클래스의 객체를 생성하게 됩니다.

이와 같이 특정 데이터 타입에만 특화된 구현을 사용할 수 있도록 하는 것을 템플릿 특화라고 하며, 당연히 위의 specialWidget과 baseWidget 의 객체는 다른 타입의 객체가 됩니다.


그리고 이런 템플릿 특화를 일부분만 특화하고 나머진 일반화 할 수 있는 부분 템플릿 특화(Partial Template Specializtion)라는 것이 있습니다.

일부 템플릿 인자는 구체적으로 명시하고, 나머지 인자들은 일반화된 채로 내버려 둡니다. 그리고 구체적으로 명시된 인자가 지정되는 경우만 특화된 코드를 적용 한다는 것이 부분특화의 특징입니다. 예를 들어 :

template <class Window>
class Widget<Window, MyController> {
    ... 부분 특화된 구현 ...
};

와 같은 부분 특화된 클래스 코드가 있는, 모든 Window 타입에 대해서는 일반화를 적용하지만, 만일 MyController가 지정되는 경우에는 특화된 코드를 적용합니다.

Widget<ModalDialog, MyController> specialWidget  과 같은 코드에서도 특화된 코드를 찾아 객체를 생성하며, Widget<BaseWindow, MyController> baseWidget과 같은 코드에서도 특화된 코드를 찾아 객체를 생성한다는 의미 입니다.

위에서도 언급했지만, 템플릿을 구체화 할 때 컴파일러는 가장 잘 맞는 후보를 정하기 위해 모든 부분의 패턴을 정확하게 파악하게 되며 우리가 염려 하는 것 이상으로 정확한 코드 적용을 해 줍니다.

하지만 템플릿 파라메터의 부분 특화는 클래스에서만 적용 할 수 있고, 멤버함수든 일반 함수든, 함수에 대해서는 적용이 불가능 합니다(일반 전체 템플릿 특화는 함수를 대상으로도 가능합니다).


참고 :
 * Template 부분 특화 시 주의 점 : http://blog.naver.com/hsg556/100011294391

'진리는어디에' 카테고리의 다른 글

ASN.1  (0) 2010.03.22
템플릿 특화를 이용한 튜플 클래스  (0) 2010.02.15
템플릿 특화(Template specialize)  (2) 2010.02.03
C++ #line은 뭐지?  (0) 2010.02.02
WideCharToMultiByte()  (0) 2009.03.20
서브(Sub) 쿼리  (4) 2009.02.26
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2010.02.03 13:37  댓글주소  수정/삭제  댓글쓰기

    요즘 검색엔진 만들면서 STL을 하나씩 구현(?) 하고 있는데... 탬플릿 어려워요

BTree

진리는어디에 2008.03.26 00:49

구현 완료

  • Insert
    • split
  • delete
    • merge
  • search

미구현
 iterate
잠이 오긴 하지만 잠시 생각 해보자.
Btree는 iteration이 상당히 까칠하다. 그럼 list를 추가하면 될듯한데..그럼 키는 트리에 유지하고 값만 리스트에 저장 하면 되겠군. 그런데 문제는 트리가 다시 밸런스를 맞추면서인데..예를 들어 2:2가 들어 오고 3:3이 들어 오고 1:1이 들어 오면 3이야 뒤에 추가 하면 되니 아무런 문제가 없지만 1:1은 2보다 작다는 것을 알 수 있으니까 2의 iterator 앞에 넣어 주면 되겠군. 대충 돌아 갈 수 있을 것 같구먼
 erase

소스 코드 :




'진리는어디에' 카테고리의 다른 글

Generation reference counting  (0) 2008.05.15
Logical clock  (2) 2008.05.02
BTree  (8) 2008.03.26
Python embedding 1 - Overview  (6) 2008.03.13
파이썬으로 utf-8 문서 읽기  (1) 2008.02.22
Python embedding 4 - PyErr_Print() 를 파일로 남기기  (4) 2008.02.15
Posted by kukuta

댓글을 달아 주세요

  1. Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2008.02.24 23:47  댓글주소  수정/삭제  댓글쓰기

    연구실 검색엔진에 쓰고 있는 b+tree가 생각나네요 ^^;;
    bucket, cache, merge가 절 괴롭힙니다.

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2008.02.25 10:31 신고  댓글주소  수정/삭제

      이터레이션이 문제야..-_-;
      stack을 안 쓰고 트리를 이터레이션 할 수 있는 방법이 없을까?
      물론 성능 저하도 그렇게 크게 없어야 하고 말이야.

    • Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2008.02.26 01:27 신고  댓글주소  수정/삭제

      b-tree에서는 문제가 되겠지만, leef-node가 전부 연결되어 있는 b+tree를 사용하면 간단하게 해결되잖아요.
      그 대표적인 개발 예가 버클리DB
      db로부터 cursor을 받아서 next() 메쏘드로 iteration하면 끝나는 이야기 아닌가요?
      뭐, 선배가 저렇게 구현을 해야 하지만...

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2008.02.26 19:20 신고  댓글주소  수정/삭제

      아놔...첨엔 이렇게 쓸 생각 안 했었지..
      그냥 주말에 하도 할 일이 없어서 옛날에 배웠던거나 만들어 볼까하다가, 만들다 보니 stl하고 비슷하게 만들자..라는 생각까지..쿨럭

      니 말대로 다 엎고 다시 B+트리 만들어야 겠다.

    • Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2008.02.26 23:16 신고  댓글주소  수정/삭제

      필요하세요? C는 있는데...

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2008.02.27 16:02 신고  댓글주소  수정/삭제

      필요하지는 않아. 무슨 프로젝트가 걸린게 아니고 그냥 주말에 심심해서 하는 일이니까..-_-;

      너한테 코드를 받아버리면 주말에 난 뭐하니?

    • Favicon of http://blog.ggamsso.wo.tc BlogIcon 깜쏘 2008.03.03 13:29 신고  댓글주소  수정/삭제

      할 거 많아요.
      제가 소스를 드린 만큼 저랑 놀아주셔야죠.

    • Favicon of https://kukuta.tistory.com BlogIcon kukuta 2008.03.03 21:26 신고  댓글주소  수정/삭제

      위키피디어나, 소스포지가면 소스 넘쳐 흘러..ㅋ
      그리고 너랑 안놀아~
      울 엄마가 남자 좋아하는 애랑은 놀지 말래