나만의 작은 도서관

[TIL][C++] 250826 MMO 서버 개발 87일차: [언리얼] 다시 복습하는 델리게이트(delegate), 이벤트(Event) 본문

Today I Learn

[TIL][C++] 250826 MMO 서버 개발 87일차: [언리얼] 다시 복습하는 델리게이트(delegate), 이벤트(Event)

pledge24 2025. 8. 26. 22:18
주의사항: 해당 글은 일기와 같은 기록용으로, 다듬지 않은 날것 그대로인 글입니다. 

[언리얼] 다시 복습하는 델리게이트(delegate)

 

델리게이트(delegate)란?

  • 델리게이트(delegate)란 임의의 함수를 저장해두었다가 필요한 시점에 호출할 수 있게 해주는 “콜백 시스템” 기능을 갖춘 방식들 중 하나이다. (콜백 시스템이라는 단어는 내가 부르기 편하기 위해 만든 단어이다.)
  • 콜백 시스템을 갖춘 다른 방법들은 1) functor, 2) function객체 3) 함수 포인터 등이 있다.

 

언리얼 델리게이트(delegate)의 장점들

 

장점 1. 안전하게 함수를 호출할 수 있다.

  • 함수 호출은 때때로 위험할 수 있다. 특정 객체의 멤버 함수를 호출할 경우, 객체가 파괴된 다음 멤버 함수를 호출하게 되면 댕글링 포인터 문제가 발생하기 때문이다.
  • 하지만 언리얼 델리게이트를 사용할때 리플렉션 시스템에 의해 관리되는 UObject 객체의 멤버함수를 추가한다면, UObject 생명주기에 연동되어 객체가 파괴되면 자동으로 해제되므로 댕글링 포인터 발생을 막을 수 있다.
    • 다른 말로 바꿔말하면 UObject로 관리되지 않는 클래스를 등록하면 안전하지 않다는 말이 되기도 한다.

 

장점 2. 동적으로 함수를 추가할 수 있다.

  • 일반적은 C++ 함수 포인터는 컴파일 타임에 바인딩되므로 런타임에 동적으로 변경하기 어렵다. 하지만 언리얼의 델리게이트는 함수들을 런타임에 식별하여 추가할 수 있다.
  • 다이나믹 델리게이트를 활용하면 블루프린트나 리플렉션을 통한 런타임 바인딩이 가능해지기도 한다.

 

장점 3. 여러 함수를 추가할 수 있다.

  • 언리얼의 델리게이트는 여러 개의 함수를 하나의 델리게이트에 추가할 수 있다. 즉, 특정 이벤트가 발생했을때 여러 함수가 호출될 수 있다는 것이다.
  • 어느 하나의 이벤트가 여러 시스템에 영향을 주어야하는 경우(Ex. 캐릭터 사망 시 UI 갱신, 점수 계산, 사운드 재생)에 유용하게 사용할 수 있다.

 

장점 4. 느슨한 결합

  • 델리게이트 방식을 사용하면 송신자(이벤트를 발생시키는 객체)와 수신자(이벤트에 반응하는 객체)간의 직접적인 의존성이 없다. 송신자는 수신자가 어떤 객체인지, 어떤 함수를 호출하는지 몰라도 이벤트를 브로드캐스트 할 수 있다.
  • 이러한 특성은 시스템 간의 결합도가 낮아져 코드를 관리하고 확장하기가 훨씬 쉬워진다.

 

델리게이트 고르는 법 요약

  함수를 딱 하나만 바인드 함수를 여러 개 바인드
네이티브에서만 바인드할거임 일반 델리게이트 멀티캐스트 일반 델리게이트
블루프린트에서도 바인드할거임 다이나믹 델리게이트 다이나믹 멀티캐스트 델리게이트

 

델리게이트 바인딩 종류

  • 기존 델리게이트 객체 → Bind()
  • 전역 C++ 함수 → BindStatic()
  • Native 클래스 멤버 함수 → BindRaw()
  • 람다 함수 → BindLambda()
  • SharedPtr 클래스 멤버 함수(잘 안 씀) → BindSP()
  • UObject 멤버 함수 → BindUObject()

 

델리게이트 실행시키기

  • Excute(): 등록된 함수들을 호출한다.
  • ExcuteIfBound(): 바인딩된 함수가 있을때만 호출한다.
  • IsBound(): 바인딩된 함수가 있는지 bool값으로 반환

 

델리게이트 사용 예제

 

클래스가 하나 있다.

class FLogWriter
{
	void WriteToLog(FString);
};

 

 

델리게이트를 선언한다.

  • 함수 시그니처를 맞추기 위해 OneParam으로 만들었고, 델리게이트 이름은 FStringDelegate로 하기로 했다.
// 델리게이트 선언을 이따구로 하는 이유는 기반 언어인 C++에 델리게이트가 없기 때문
// 언리얼은 델리게이트 시스템을 구축하기 위해 이따구로 매크로를 만든것이다.
DECLARE_DELEGATE_OneParam(FStringDelegate, FString);

 

 

델리게이트를 배치한다.

  • 보통 델리게이트는 이벤트의 주체가 되는 클래스안에 배치된다. 예를 들어 플레이어가 맞았을때 발동해야하는 이벤트는 플레이어 클래스에 들어간다.
  • 헷갈릴 수 있는데, FStringDelegate는 델리게이트 클래스 이름, WriteToLogDelegate는 객체 이름이 되겠다.
class FMyClass
{
	FStringDelegate WriteToLogDelegate;
};

 

 

만든 델리게이트에 함수를 바인딩한다.

  • 여기서는 SharedPtr 타입을 통해 함수를 바인딩하려고 하므로, 그에 맞는 BindSP 함수로 바인딩하는 모습이다. 첫번째 인자로는 객체 포인터가, 두번째 인자로는 함수 포인터가 들어간다.
TSharedRef<FLogWriter> LogWriter(new FLogWriter());

WriteToLogDelegate.BindSP(LogWriter, &FLogWriter::WriteToLog);

 

 

원하는 시점에 델리게이트를 실행시킨다.

  • 델리게이트를 실행시킬 시점이 되었다면 Execute를 통해 실행시킨다. WriteToLogDelegate는 OneParam 델리게이트 이므로 인자를 하나 넣어줘야한다.(당연히 같은 인자 타입을 넣어줘야한다.)
WriteToLogDelegate.Execute(TEXT("델리게이트 실행!"));
  • Excute의 경우 호출시 assert가 발동되는데, assert를 통과하지 못하면 그대로 시스템이 멈추므로, 이런 경우를 피하기 위해 아래와 같이 안전하게 피해가도록 한다.
    • assert는 비정상적인 실행이 감지되었을때 크래시를 내는 함수이다. 디버깅에 유용하지만 개발 단계가 아닌 서비스 중에 크래시가 나면 큰일이기 때문에 보통은 피해간다.
WriteToLogDelegate.ExecuteIfBound(TEXT("델리게이트 실행!"));

 

 

결과

  • 위의 경우 SharedPtr로 바인드했기 때문에 객체가 먼저 죽어있는 경우는 없으므로, 무조건 실행될 것이다. 실행되는건 LogWrite객체의 WriteLog() 함수 하나이다.

 

 

BlueprintAssignable

  • C++에서 선언한 다이나믹 델리게이트를 블루프린트에서 함수를 바인드하려고 할때 추가하는 UPROPERTY의 지정자
// MyActor.h
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyActor.generated.h"

// 동적 멀티캐스트 델리게이트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOnSomethingHappened);

UCLASS()
class AMyActor : public AActor
{
    GENERATED_BODY()

public:
    // BlueprintAssignable → 블루프린트에서 바인딩 가능
    UPROPERTY(BlueprintAssignable, Category="Event")
    FOnSomethingHappened OnSomethingHappened;

    // 어떤 이벤트가 발생했을 때 호출되는 함수
    UFUNCTION(BlueprintCallable, Category="Event")
    void TriggerEvent();
};

// MyActor.cpp
#include "MyActor.h"

void AMyActor::TriggerEvent()
{
    // 델리게이트에 바인딩된 모든 함수 실행
    OnSomethingHappened.Broadcast();
}

 

 

블루프린트에서 함수 바인드하기

  • AMyActor를 상속한 블루프린트를 연다.
  • 이벤트 그래프에서 MyActor 인스턴스를 선택 → 디테일 패널에서 델리게이트 변수이름으로 된 이벤트가 표시됨(OnSomethingHappened)
    • C++에서 UPROPERTY(BlueprintAssignable)로 선언했기 때문에 표시되는 것!
  • 버튼을 눌러 바인드된 이벤트 노드를 생성
  • 원하는 로직을 추가한다.

 

이벤트(Event)

  • 멀티캐스트 델리게이트와 유사한 기능.
  • 외부에서 실행( Broadcast 또는 Excute 함수 호출) 불가. 이벤트를 선언한 클래스를 통해서만 실행 가능.
// 이벤트 선언
DECLARE_EVENT(AMyActor, FOnSomethingHappened)

class AMyActor : public AActor
{
public:
    FOnSomethingHappened& OnSomethingHappened() { return SomethingHappened; }

    void TriggerEvent()
    {
        SomethingHappened.Broadcast(); // 내부에서만 가능
    }

private:
    FOnSomethingHappened SomethingHappened;
};

// 구독하는 쪽
MyActor->OnSomethingHappened().AddUObject(this, &UMyClass::HandleSomething);

 

 

구분  Delegate Event(DECLARE_EVENT)
바인딩 개수 싱글(기본), 멀티캐스트 가능 항상 멀티캐스트
접근성 누구나 Bind/Execute 가능 구독자는 Add~만 가능, Broadcast는 오직 선언한 클래스 내부에서만 가능
사용 의도 콜백 함수 저장/호출 이벤트 퍼블리셔-리스너 패턴 (발생 권한은 내부 전용, 외부는 구독 전용)
안전성 실행/호출 모두 외부에서 가능 → 잘못 쓰면 위험 호출/발생은 캡슐화, 더 안전한 API 설계

 

 

정리

  • Delegate → 범용적, 외부에서도 Broadcast 가능 (자유도 ↑, 실수 위험 ↑).
  • Event → Observer 패턴 전용, 발생 권한은 내부 한정, 외부는 구독만 가능 (안전한 설계).