델리게이트 (Delegate) 란?

*멀티 케스트나 다이나믹 케스트에 대한 재작업 필요*

Delegate 단어 자체는 “위임” 이라는 의미가 있다. 함수의 작동 작업을 Delegate에 위임함으로써, 일관된 관점을 통해 처리하도록 도와주는 하나의 인터페이스라고 할 수 있다.

구현한 함수나 객체들을 Delegate로 묶으면, 그 함수나 객체들을 모르는 곳에서도 Delegate만 알면 된다는 의미이다.

언리얼에서 델리게이트는 C++ 오브젝트 상의 멤버 함수 호출을 일반적, 유형적인 안전한 방식으로 할 수 있게 도와주는 기능이다.

델리게이트를 사용하여 임의 오브젝트의 멤버 함수에 동적으로 바인딩 시킬수도 있고, 함수를 호출하는 곳에서 오브젝트의 유형을 몰라도 함수 실행이 가능하다.

언리얼 델리게이트 오브젝트는 복사에도 완벽히 안전하며, 델리게이트는 Heap에 메모리를 할당하기 때문에 참조전달하는 것이 권장된다.

언리얼 델리게이트는 Single Cast와 Multi Cast 모두 지원되며, 디스크에 안전하게 직렬화 (Serialize) 시킬 수 있는 Dynamic 델리게이트도 가능하다.

기본적인 델리게이트 선언 방법

델리게이트의 선언은 제공되어 있는 선언 매크로 중 하나를 사용해 이루어진다. 사용도는 매크로는 델리게이트에 바인딩되는 함수의 시그니처에 따라 결정된다.

시스템에서는 델리게이트 유형을 선언해 낼 수 있는 범용 함수 시그니처의 다양한 조합을 미리 정의해두고, 이를 통해 반환값과 매개변수에 대한 유형 이름만 넣어주면 된다.

아래와 같은 항목들이 포함된 어떠한 조합에 대해서도 델리게이트 시그니처가 지원된다.

  • 값을 반환하는 함수
  • 페이로드 변수 4 개 까지
  • 함수 파라미터 8 개 까지
  • const 로 선언된 함수

델리게이트 선언에 사용할 수 있는 매크로들은 이렇다.

리턴 값이 없는 델리게이트 매크로

  • DECLARE_DELEGATE(DelegateName)
  • DECLARE_DELEGATE_OneParam(DelegateName, Param1Type)
  • DECLARE_DELEGATE_TwoParam(DelegateName, Param1Type, Param2Type)
  • DECLARE_DELEGATE_<Num>Param(DelegateName, Param1Type, Param2Type, …)

리턴 값이 있는 델리게이트 매크로

  • DECLARE_DELEGATE_RetVal(RetValType, DelegateName)
  • DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type)
  • DECLARE_DELEGATE_RetVal_TwoParam(RetValType, DelegateName, Param1Type, Param2Type)
  • DECLARE_DELEGATE_RetVal_<Num>Param(RetValType, DelegateName, Param1Type, Param2Type, …)
//리턴 값이 없는 델리게이트
DECLARE_DELEGATE(FTestDelegate)
DECLARE_DELEGATE_OneParam(FTest2Delegate, int)

//리턴 값이 있는 델리게이트
DECLARE_DELEGATE_RetVal(int, FTestRetValDelegate)
DECLARE_DELEGATE_Retval_OneParam(int, FTest2RetValDelegate, int)

//기타 등등

멀티캐스트, 다이나믹, 래핑된 델리게이트에 대한 변종 매크로도 제공된다.

델리게이트 시그니처 선언은 글로벌 영역, 네임스페이스 안 그리고, 함수 본문을 제외한 클래스 선언부 속에도 존재 가능하다.

델리게이트 바인딩

델리게이트 시스템은 특정 유형의 오브젝트를 이해하고 있으며, 이러한 오브젝트를 사용할 때는 추가적으로 사용할 수 있는 기능들이 있다.

UObject나 공유 포인터 클래스 멤버에 델리게이트를 바인딩하는 경우에 델리게이트 시스템은 그 오브젝트에 대한 약한 레퍼런스를 유지할 수 있어, 델리게이트 진행중 오브젝트가 소멸된 경우 IsBound() 함수나 ExecuteIfBound() 함수를 호출하여 예외처리를 할 수 있다.

Bind()기존 델리게이트 오브젝트에 바인딩한다.
BindStatic()raw C++ 포인터 글로벌 함수 델리게이트를 바인딩한다.
BindRaw()날(raw) C++ 포인터 델리게이트에 바인딩한다. nullptr은 어떠한 종류의 레퍼런스도 사용하지 않아, 만약 오브젝트가 델리게이트 치하에서 삭제된 경우 호출하기가 안전하지 않을 수도 있다. Execute() 호출시에는 조심해야한다.
BindSP()공유 포인터-기반 멤버 함수 델리게이트에 바인딩한다. 공유 포인터 델리게이트는 오브젝트로의 약한 레퍼런스를 유지한다. ExecuteIfBound() 로 호출할 수 있다.
BindUObject()UObject 기반 멤버 함수 델리게이트를 바인딩한다. UObject 델리게이트는 오브젝트로의 약한 레퍼런스를 유지한다. ExecuteIfBound() 로 호출할 수 있다.
UnBind()이 델리게이트 바인딩을 해제한다.

델리게이트 바인딩을 진행할 때, 페이로드 데이터를 매개변수로 같이 전해줄 수 있다.

여기서 페이로드 데이터란바인딩된 함수를 불러낼 때, 직접 전해지는 변수들이다. 바인딩 시간에 델리게이트 자체적으로 매개변수를 보관할 수 있어 매우 유용하다.

페이로드 데이터를 바인드 하는 예

FTestDelegate.BindRaw(&SomeFunction, true, 20);

델리게이트 실행법

델리게이트에 바인딩된 함수는 델리게이트의 Execute() 함수를 호출해 실행하면 된다. 그러나 호출 전에 바인딩이 먼저 되어있는지 확인을 해야한다. 바인드 되어있지 않은 델리게이트를 호출할 경우, 호출된 인스턴스의 메모리에 위협이 될 수 있다.

델리게이트를 실행하기 전에 IsBound()를 호출해 검사해볼 수 있다. 또한 반환값이 없는 void형 함수의 경우에는 ExecuteIfBound() 함수를 사용해 델리게이트를 호출할 수 있다.

델리게이트 호출 예

/ Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"
#include "GameFramework/Actor.h"
#include "DelegateTest.generated.h"

DECLARE_DELEGATE(FVoidDelegate);

UCLASS()
class TEST_API ADelegateTest: public AActor
{
	GENERATED_BODY()
	
public:	
	ADelegateTest();
        FVoidDelegate voidDelegate;
        virtual void PostInitializeComponents() override
        {
            Super::PostInitializeComponents();
            voidDelegate.Bind(this, ADelegateTest::SomeFunction());
        }
        virtual void BeginPlay() override
        {
            Super::BeginPlay();
            voidDelegate.Execute();
        }
        UFUNCTION()
        void SomeFunction() {LOG(Warning, TEXT("Hello Delegate!"));
};