본문 바로가기

진리는어디에/C++

[C++] C++에서 C# delegate 만들기

들어가며

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++의 템플릿을 이해하는데 많은 도움이 될 것이라 생각한다.

부록 1. 같이 보면 좋은 글

유익한 글이었다면 공감(❤) 버튼 꾹!! 추가 문의 사항은 댓글로!!