사전적 의미의 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 신고  댓글주소  수정/삭제  댓글쓰기

    잘보고갑니다~