언리얼 C++ 프로그래밍

[언리얼 C++] 싱글캐스트 델리게이트

언린이 2021. 6. 29. 21:44

델리게이트는 C++ 오브젝트 상의 멤버 함수를 가리키고 실행시키는 데이터 유형입니다.

 

델리게이트를 사용하여 C++ 오브젝트 상의 멤버 함수를 안전한 방식으로 호출할 수 있습니다. 델리게이트를 사용하여 오브젝트의 멤버 함수에 동적으로 바인딩 시킬 수 있으며, 해당 함수를 다른 오브젝트에서 호출할 수 있습니다. 호출하는 오브젝트에서 멤버 함수가 정의된 오브젝트의 유형을 몰라도 가능합니다.

 

델리게이트 오브젝트는 값 복사를 해도 안전하지만 추천하지는 않습니다. Heap에 메모리를 할당해야 하기 때문입니다. 그래서 가급적이면 델리게이트는 항상 참조 전달을 해줘야 합니다.

 

델리게이트의 종류에는 싱글캐스트와 멀티캐스트가 존재하고, 디스크에 안전하게 Serialize 시킬 수 있는 다이내믹 델리게이트 또한 존재합니다. 이번 글에서는 싱글캐스트 델리게이트의 사용법에 대해 알아보겠습니다.

 

 

1. 싱글캐스트 델리게이트 선언하기

언리얼 엔진에서는 싱글캐스트 델리게이트 선언을 위해서 여러가지 매크로를 제공하고 있습니다.

사용되는 매크로는 델리게이트에 바인딩되는 함수의 시그너처에 따라 결정됩니다.

 

DECLARE_DELEGATE(DelegateName) // return : void, param num : 0

DECLARE_DELEGATE_OneParam(DelegateName, Param1Type) // return : void, param num : 1

DECLARE_DELEGATE_TwoParams(DelegateName, Param1Type, Param2Type) // return : void, param num : 2

DECLARE_DELEGATE_<Num>Params(DelegateName, Param1Type, Param2Type, ...) // return : void, param num : Num

DECLARE_DELEGATE_RetVal(RetValType, DelegateName) // return : RetValType, param num : 0

위의 코드처럼 바인딩되는 함수의 파라미터 개수나 반환 타입에 따라 다양한 싱글캐스트 델리게이트를 선언할 수 있습니다.

 

 

2. 싱글캐스트 델리게이트 바인딩하기

언리얼 엔진에서 바인딩되는 오브젝트 유형에 따라 다양한 바인딩 문법을 제공해주고 있습니다.

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

위의 문법들을 사용하여 싱글캐스트 델리게이트에 오브젝트 멤버 함수를 바인딩할 수 있습니다.

 

 

3. 싱글캐스트 델리게이트 실행하기

함수 설명
Execute() 싱글캐스트 델리게이트에 바인딩된 함수를 실행합니다.
ExecuteIfBound() 싱글캐스트 델리게이트에 바인딩된 함수를 확인하고 존재하면 함수를 실행합니다.
IsBound() 싱글캐스트 델리게이트에 바인딩된 함수를 확인합니다.

델리게이트에 바인딩된 함수는 델리게이트의 Execute() 함수를 호출하여 실행합니다. 델리게이트를 실행하기 전 바인딩되어 있는 함수가 존재하는지 반드시 확인해야 합니다. 이는 코드 안전성을 보장해주기 위함인데, 바인딩되지 않은 델리게이트를 실행시키면 일부 인스턴스에서 메모리 낭비가 발생할 수 있기 때문입니다.

그래서 델리게이트를 실행하기 전에 항상 실행해도 안전한 지를 확인해야 합니다. 델리게이트가 실행되어도 안전한 지는 IsBound() 함수를 호출하여 검사해 볼 수 있습니다.

또한 델리게이트의 안전성을 검사하고 실행하는 함수는 ExecuteIfBound() 함수를 제공해줍니다. 하지만 이 함수는 반환값이 없는 델리게이트에 대해서만 호출할 수 있으며, 출력 파라미터는 초기화되지 않을 수 있다는 점을 주의해야 합니다.

 

 

4. 사용 예제

class FLogWriter
{
    void PrintLog(FString);
};

위 예제 클래스의 PrintLog 함수는 로그를 출력하는 함수이며, 아무 클래스에서나 호출이 가능해야 합니다.

PrintLog 함수를 호출하기 위해 해당 함수의 시그너처에 맞는 델리게이트 유형을 생성해야 합니다.

 

DECLARE_DELEGATE_OneParam(FLogDelegate, FString);

위 매크로를 사용하여 선언하면 델리게이트의 이름은 FLogDelegate가 되고 FString 타입의 파라미터 하나를 받게 됩니다.

 

class FSampleTest
{
    FLogDelegate PrintLogDelegate;
};

이제 다른 클래스에서 FLogDelegate 타입의 델리게이트를 선언해줍니다. 위 클래스에서 델리게이트에 대해 아는 것은 함수 시그너처 뿐입니다. 이제 해당 델리게이트에 함수를 바인딩해보겠습니다.

 

TSharedRef<FLogWriter> LogWriter(new FLogWriter());

PrintLogDelegate.BindSP(LogWriter, &FLogWriter::PrintLog);

위의 코드처럼 클래스의 함수에 델리게이트를 동적으로 바인딩할 수 있습니다.

 

BindSP의 SP는 공유 포인터를 뜻하는데, 공유 포인터에 소유된 오브젝트에 바인딩하고 있기 때문에 해당 문법을 사용하였습니다.

 

이제 FLogWriter 클래스에 대해 어떠한 정보가 없을지라도 FSampleTest 클래스에서 PrintLog 함수를 호출할 수 있습니다.

 

PrintLogDelegate.Execute(TEXT("델리게이트 실행"));

Execute() 함수를 사용하여 호출할 수도 있고,

 

PrintLogDelegate.ExecuteIfBound(TEXT("델리게이트 실행"));

또한, ExecuteIfBound() 함수를 사용하여 델리게이트의 안전성을 검사한 후 호출할 수도 있습니다.

델리게이트에 함수를 바인딩하기 전 Execute() 함수를 호출하면 assert가 발생합니다. 그러므로 되도록 ExecuteIfBound() 함수를 사용하는 것이 좋습니다.