들어가며
C#을 사용해본 분들은 delegate라는 용어에 익숙할 것이다. 사전적 의미의 delegate는 '대리자'로써 뭔가를 대신 해준다는 의미고, C#에서의 delegate는 일종의 함수 리스트로써 입/출력 인자가 동일한 함수들을 일괄 호출하는데 사용된다.
일종의 '함수 리스트' 구조인 delegate는 pub/sub과 같은 디자인 패턴에서 매우 유용하게 사용된다. 구독을 하는 개체들이 delegate에 자신의 콜백 함수를 등록하고, 이벤트 발생 시 publisher는 단순히 delegate를 호출하여 전체 구독 개체들에게 이벤트를 일괄 전파한다.
C#은 delegate를 이용하여 위의 디자인 패턴을 아주 쉽게 구현할 수있다. 구독 개체는 static 함수, 클래스 멤버 함수 또는 람다 등 함수의 타입에 상관 없이 입/출력 인자의 형태만 같다면 아무런 제약 없이 등록 가능하다.
필자가 진행하는 C++ 프로젝트에서도 C#의 delegate와 같이 높은 추상화 레벨을 가진 콜백 함수 리스트가 필요하여 이번 포스트를 통해 공유해 본다.
요구 사항
- 일반 함수, 람다, 클래스 멤버 함수, 클래스 static 함수를 등록 가능해야 한다.
- 편의를 위해, std::bind, std::placeholders 와 같은 기능을 사용하지 않아도 됐으면 한다.
- 등록된 함수들을 일괄 호출 할 수 있어야 한다.
- 등록 된 함수들을 개별 삭제 할 수 있어야 한다.
- 리턴 값을 가진 함수도 등록 할 수 있어야 한다.
단, C#과 같이 리스트에 가장 마지막에 위치한 함수의 리턴 값을 리턴한다.
사용 예
Delegate 선언
#include "Delegate.h"
// 소스 파일 : https://github.com/ChoiIngon/blog/blob/master/207/Delegate.h
int main()
{
// template< class R, class... Args >
// class Delegate<R(Args...)>;
Delegate<std::size_t(const std::string&)> delegate;
Delegate 선언은 std::function의 함수 시그니쳐와 동일하다. 위 예제에서는 'const std::string&' 인자를 받고 std::size_t 타입을 리턴하는 함수를 등록할 수 있는 delegate를 선언 한다.
일반 함수 등록/해제
#include "Delegate.h"
// 소스 파일 : https://github.com/ChoiIngon/blog/blob/master/207/Delegate.h
// 전역 함수
std::size_t free_function(const std::string& msg)
{
std::cout << "\tgreeting message \'" << msg << "\' from " << __FUNCTION__ << std::endl;
return msg.length();
}
int main()
{
....
delegate += free_function; // 일반 함수 등록
... or ...
delegate.push_back(free_function);
delegate -= free_function; // 등록 해제
... or ...
delegate.erase(free_function);
람다(lambda) 등록 /해제
#include "Delegate.h"
// 소스 파일 : https://github.com/ChoiIngon/blog/blob/master/207/Delegate.h
int main()
{
.....
auto lambda_function = [](const std::string& msg)
{
std::cout << "\tgreeting message \'" << msg << "\' from " << __FUNCTION__ << std::endl;
return msg.length();
};
delegate += lambda_function; // 람다 함수 등록
... or ...
delegate.push_back(lambda_function);
delegate -= lambda_function // 람다 등록 해제
... or ...
delegate.erase(lambda_function);
클래스 멤버/static 함수 등록/해제
아래는 클래스의 멤버 함수와 static 함수를 등록/해제하는 예제다. 주목해야 할 부분은 다른 타입의 함수들은 +=, -= 연산자를 통해 등록과 해제가 가능했으나, 클래스 멤버함수는 호출을 위해 클래스 객체 또한 필요하므로 위 오퍼레이터를 사용하지 못하고 push_back, erase 함수를 통해 등록/해제 해야 한다.
#include "Delegate.h"
// 소스 파일 : https://github.com/ChoiIngon/blog/blob/master/207/Delegate.h
class Foo
{
public:
std::size_t member_function(const std::string& msg)
{
std::cout << "\tgreeting message \'" << msg << "\' from " << __FUNCTION__ << std::endl;
return msg.length();
}
static std::size_t static_function(const std::string& msg)
{
std::cout << "\tgreeting message \'" << msg << "\' from " << __FUNCTION__ << std::endl;
return msg.length();
}
};
int main()
{
.....
Foo foo;
delegate.push_back(&Foo::member_function, &foo); // 멤버 함수 등록
delegate.erase(&Foo::member_function, &foo); // 멤버 함수 해제
delegate += &Foo::static_function; // static 함수 등록
... or ...
delegate.push_back(&Foo::static_function);
delegate -= &Foo::static_function
... or ...
delegate.erase(&Foo::static_function);
마치며
이번 포스트는 delegate 클래스를 어떻게 만들었냐 보다는 사용 법에 대해 집중하여 작성되었다(나중에 내가 보고 사용할 수 있게). 내부적으로 variadic template parameter 를 이용한 std::placeholder 추론, std::function의 기능 사용등 살펴 볼만한 것들이 있으니 [여기]에서 Delegate 클래스의 코드를 살펴 보는 것도 C++의 템플릿을 이해하는데 많은 도움이 될 것이라 생각한다.