Unreal Engine 5

How to Bind a C++ Function to a Delegate

In modern software development, delegates are a crucial concept that goes beyond individual platforms or programming languages. Born from the need for flexible and efficient event handling and method delegation, delegates have become essential in many contemporary programming paradigms. This is especially true in game development with the Unreal Engine, where they're key in event processing and dynamic method invocation.

Fundamentally, a delegate is a wrapper for a function reference or pointer. If you're new to function pointers and lambda functions in C++, consider reading this article on Function Pointers and Lambda Functions in C++. They form the basis for delegates in the Unreal Engine. In software development, delegates let a program define a method that can change any time during the program's execution. This offers great flexibility by abstracting and delegating method calls without knowing the exact implementation or context beforehand. Delegates are the foundation of my event system in Unreal Engine projects, detailed in this article about my Event Manager in Unreal Engine in C++.

Delegates are found not just in Unreal Engine but also in many other languages and frameworks. In C#, for instance, they're integral to the .NET Framework, used in event handling and asynchronous programming. Similar concepts appear in Java as interfaces and anonymous classes, or as callbacks in JavaScript, underscoring their versatility and importance across different programming environments.

A notable use of delegates is in the Windows Presentation Foundation (WPF) in C#. WPF, a framework for graphical user interfaces in .NET, uses delegate-based events for UI element interactions, like buttons. Developers can bind their event handlers to these events to trigger specific actions. For example, binding an event handler to a button's Click event triggers an action when the user clicks the button. This flexibility lets developers use a variety of UI elements and enhance them with custom functions.

While delegates are practical, they can be frustrating due to challenges in binding functions to them. In this article, I aim to demystify this by explaining, with clear examples, how to define delegates and bind functions to them.

What Do Delegates Look Like in Unreal Engine?

Before I dive straight into the topic and explain the details, let me first give you a rough overview with a visual example of what using a Delegate in Unreal Engine might look like. Then, we'll go into detail for each section and look at our options for using Unreal Engine Delegates.

To demonstrate Delegates clearly, I've thought of a simple example. Admittedly, it's not very clever and not really useful, but it should illustrate the concept of Delegates in the Unreal Engine quite well.

There's no real-world application, but I've built a character and stripped it of all its functionality. I only need keyboard input to somehow show the concept of Delegates.

In my example, I will implement an action that can be triggered with an action key, like the Enter key, and the action can be switched out with the number keys 1-3. So, with keys 1 to 3, you can switch between actions. Once you've decided on an action, you can trigger it with the Enter key.

Step 1: Declaration of the Delegate

First, we define a Delegate type in our C++ code. This Delegate type determines what kind of functions can be called. For our example, we need a Delegate with no return value and no parameters. We declare this Delegate as follows:

DECLARE_DELEGATE(FMyActionDelegate);

I will list the different Delegate declarations and possible parameters later in the article, so let's start with this parameter-less declaration for the small example.

Step 2: Definition of the Delegate

After declaring the function, we need to define a concrete function that can be executed and an instance of the Delegate to which we can bind the function. So, in our character class, we then define an instance of this Delegate, which we call currentAction. We can then bind a function to it:

#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "DelegateDemoCharacter.generated.h"

// Declares a new Log Category for this class
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);

// Declares a new Delegate type named FMyActionDelegate
DECLARE_DELEGATE(FMyActionDelegate);

// ADelegateDemoCharacter is an extension of the ACharacter class
UCLASS(config = Game)
class ADelegateDemoCharacter : public ACharacter
{
    GENERATED_BODY()

public:
    // Constructor of the class
    ADelegateDemoCharacter(const FObjectInitializer& ObjectInitializer);

protected:
    // Methods for binding actions to the Delegate
    void BindActionOne();
    void BindActionTwo();
    void BindActionThree();

    // Function to execute the action bound by the Delegate
    void ExecuteAction();

    // Three actions that can be bound with keys 1, 2, and 3
    void ActionOne();
    void ActionTwo();
    void ActionThree();

protected:
    // Overrides the SetupPlayerInputComponent method to configure inputs
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;

    // BeginPlay is called at the start of the game
    virtual void BeginPlay();

public:
    // The Delegate that stores the current action
    FMyActionDelegate currentAction;
};

Step 3: Binding a Function to the Delegate

Now, we just need to bind a function to the Delegate that we have defined. In this case, we can do this with the BindUObject function. We give the function a pointer to the class object and a method to be called. In the previously mentioned article on Functionpointer, I explained why we need to pass a pointer to the instance of the class whose method we want to call. So, if that seems unclear to you, I recommend you check out the article.

currentAction.BindUObject(this, &ADelegateDemoCharacter::ActionOne);

Through this call, the function ActionOne of the class ADelegateDemoCharacter is bound to the Delegate currentAction.

Step 4: Executing the Delegate

Now we need to execute the function bound to the currentAction Delegate. For this, we can use the ExecuteIfBound function:

currentAction.ExecuteIfBound();

When a function is bound to currentAction, it is called upon executing ExecuteIfBound. In this case, the ADelegateDemoCharacter::ActionOne() method would be executed.

It's important to check if a function is bound to the Delegate before executing it. This can be done either with the ExecuteIfBound() function, or you can check beforehand with:

if(currentAction.IsBound())
{
    currentAction.Execute();
}

This basically describes how a Delegate works. Now, what's still to know is that there are different Delegates, which can expect different parameters, and there are various ways to bind UObjects, functions, methods, and lambdas to a Delegate.

Complete Example Overview

Finally, here's the complete functionality of the example, before we deal with the different variations of Delegates:

// Include statements add the necessary header files needed for the class.
#include "DelegateDemoCharacter.h"
#include "GameFramework/Controller.h"
#include "EnhancedInputComponent.h"

// Here we define the Log Category for this class.
DEFINE_LOG_CATEGORY(LogTemplateCharacter);

//////////////////////////////////////////////////////////////////////////
// ADelegateDemoCharacter

// Class constructor
ADelegateDemoCharacter::ADelegateDemoCharacter(const FObjectInitializer& ObjectInitializer)
    : Super(ObjectInitializer)
{
}

// BeginPlay is called when the game starts or the character is spawned.
void ADelegateDemoCharacter::BeginPlay()
{
    // Call the base class implementation of BeginPlay
    Super::BeginPlay();
}

//////////////////////////////////////////////////////////////////////////
// Input

// This function is used to configure the player's inputs.
void ADelegateDemoCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    // Check if the InputComponent is a UEnhancedInputComponent
    if (UEnhancedInputComponent* EnhancedInputComponent = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        // Here we bind the key actions to our methods.
        InputComponent->BindAction("Action_1", IE_Pressed, this, &ADelegateDemoCharacter::BindActionOne);
        InputComponent->BindAction("Action_2", IE_Pressed, this, &ADelegateDemoCharacter::BindActionTwo);
        InputComponent->BindAction("Action_3", IE_Pressed, this, &ADelegateDemoCharacter::BindActionThree);
        InputComponent->BindAction("Action_Execute", IE_Pressed, this, &ADelegateDemoCharacter::ExecuteAction);
    }
    else
    {
        // Error message if no UEnhancedInputComponent is found
        UE_LOG(LogTemplateCharacter, Error, TEXT("'%s' Failed to find an Enhanced Input component! This template is built to use the Enhanced Input system. If you intend to use the legacy system, then you will need to update this C++ file."), *GetNameSafe(this));
    }
}

// Binds ActionOne to the Delegate.
void ADelegateDemoCharacter::BindActionOne()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Switched Binding to Action One"));
    currentAction.BindUObject(this, &ADelegateDemoCharacter::ActionOne);
}

// Binds ActionTwo to the Delegate.
void ADelegateDemoCharacter::BindActionTwo()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Switched Binding to Action Two"));
    currentAction.BindUObject(this, &ADelegateDemoCharacter::ActionTwo);
}

// Binds ActionThree to the Delegate.
void ADelegateDemoCharacter::BindActionThree()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Switched Binding to Action Three"));
    currentAction.BindUObject(this, &ADelegateDemoCharacter::ActionThree);
}

// This function is called to execute the action currently bound to the Delegate.
void ADelegateDemoCharacter::ExecuteAction()
{
    // Executes the Delegate if it's bound.
    currentAction.ExecuteIfBound();
}

// These functions represent the actions that can be triggered by keys 1, 2, and 3.
void ADelegateDemoCharacter::ActionOne()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Action One"));
}

void ADelegateDemoCharacter::ActionTwo()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Action Two"));
}

void ADelegateDemoCharacter::ActionThree()
{
    UE_LOG(LogTemplateCharacter, Log, TEXT("Action Three"));
}

Here, several keys are bound to functions. When key 1 on the keyboard is pressed, ADelegateDemoCharacter::BindActionOne is executed. This function binds a new function ADelegateDemoCharacter::ActionOne to the currentAction Delegate. Similar is true for ADelegateDemoCharacter::BindActionTwo and ADelegateDemoCharacter::BindActionThree. When the key mapped to Action_Execute is pressed (in my example, the Enter key), the function bound to the Delegate is executed, either ADelegateDemoCharacter::ActionOne, ADelegateDemoCharacter::ActionTwo, or ADelegateDemoCharacter::ActionThree. The keys do nothing more than simply print a line in the console.

In this example, we have seen how a Delegate in Unreal Engine can be used to flexibly respond to different player interactions. This concept allows us to make our code modular and adaptable by dynamically changing our character's responses to key inputs, depending on the context.

How to Declare Delegates?

I already mentioned the simplest form of defining a Delegate:

DECLARE_DELEGATE(FMyActionDelegate);

Let's go into more detail here. What options does Unreal offer for declaring such Delegates?

From the Unreal documentation on Delegates and Lambda Functions, I find this table that lists how to declare Delegates that can take various parameters and have different return values:

Function signature Declaration macro
void Function() DECLARE_DELEGATE(DelegateName)
void Function(Param1) DECLARE_DELEGATE_OneParam(DelegateName, Param1Type)
void Function(Param1, Param2) DECLARE_DELEGATE_TwoParams(DelegateName, Param1Type, Param2Type)
void Function(Param1, Param2, ...) DECLARE_DELEGATE_<Num>Params(DelegateName, Param1Type, Param2Type, ...)
<RetValType> Function() DECLARE_DELEGATE_RetVal(RetValType, DelegateName)
<RetValType> Function(Param1) DECLARE_DELEGATE_RetVal_OneParam(RetValType, DelegateName, Param1Type)
<RetValType> Function(Param1, Param2) DECLARE_DELEGATE_RetVal_TwoParams(RetValType, DelegateName, Param1Type, Param2Type)
<RetValType> Function(Param1, Param2, ...) DECLARE_DELEGATE_RetVal_<Num>Params(RetValType, DelegateName, Param1Type, Param2Type, ...)

The table is nice, and it explains the basic concept, but I'm sure, like me, you like to have concrete implementations as examples that you can simply take over. That's a lot, so let's start with the first three lines:

Delegate Without Parameters:

// Declaration of the Delegate
DECLARE_DELEGATE(MySimpleDelegate);

// Example of a function that corresponds to the Delegate
void MyFunction() {
    // Implementation of the functionality
    std::cout << "MyFunction was called." << std::endl;
}

// Using the Delegate:

MySimpleDelegate MyDelegate; // Definition

MyDelegate.BindStatic(&MyFunction); // Binding a function

MyDelegate.ExecuteIfBound(); // Executing the bound function

We've already seen this example above. For completeness, I wanted to run through it again.

Delegate with One Parameter:

You can also define a Delegate so that it expects a parameter when called. You just need to use the appropriate macro and pass the type as a parameter to the macro. The parameter type can be freely chosen. For example, you can simply expect an integer as a parameter and you just have to use the DECLARE_DELEGATE_OneParam macro to declare the Delegate:

// Declaration of the Delegate
DECLARE_DELEGATE_OneParam(MyIntDelegate, int);

// Example of a function that corresponds to the Delegate
void MyIntFunction(int Value) {
    // Implementation of the functionality with UE_LOG
    UE_LOG(LogTemp, Log, TEXT("Int Number: %d"), Value);
}

// Using the Delegate:
MyIntDelegate MyIntDelegateInstance; // Definition
MyIntDelegateInstance.BindStatic(&MyIntFunction); // Binding a function
MyIntDelegateInstance.ExecuteIfBound(42); // Executing the bound function

Of course, this also works with other parameter types, like Floats:

// Declaration of the Delegate
DECLARE_DELEGATE_OneParam(MyFloatDelegate, float);

// Example of a function that corresponds to the Delegate
void MyFloatFunction(float Number) {
    // Implementation of the functionality with UE_LOG
    UE_LOG(LogTemp, Log, TEXT("Float Number: %f"), Number);
}

// Using the Delegate:
MyFloatDelegate MyFloatDelegateInstance; // Definition
MyFloatDelegateInstance.BindStatic(&MyFloatFunction); // Binding a function
MyFloatDelegateInstance.ExecuteIfBound(3.14f); // Executing the bound function

or even Strings:

DECLARE_DELEGATE_OneParam(MyFStringDelegate, FString);

void MyFStringFunction(const FString& Text) {
    // Implementation of the functionality with FString
    UE_LOG(LogTemp, Log, TEXT("Text: %s"), *Text);
}

MyFStringDelegate MyFStringDelegateInstance; // Definition
MyFStringDelegateInstance.BindStatic(&MyFStringFunction); // Binding a function
MyFStringDelegateInstance.ExecuteIfBound(TEXT("A FString")); // Executing the bound function

Delegate with Two Parameters:

You can design Delegates to process more than one parameter. Let's look at an example of how to declare and use a Delegate with two parameters, in this case, an int and an FString:

// Declaration of the Delegate with two parameters
DECLARE_DELEGATE_TwoParams(MyIntAndFStringDelegate, int, FString);

// Example of a function that corresponds to the Delegate
void MyIntAndFStringFunction(int Number, const FString& Text) {
    // Implementation of the functionality with UE_LOG
    UE_LOG(LogTemp, Log, TEXT("Number: %d, Text: %s"), Number, *Text);
}

// Using the Delegate:
MyIntAndFStringDelegate MyIntAndFStringDelegateInstance; // Definition
MyIntAndFStringDelegateInstance.BindStatic(&MyIntAndFStringFunction); // Binding a function
MyIntAndFStringDelegateInstance.ExecuteIfBound(42, TEXT("Hello Unreal")); // Executing the bound function

The parameter type is interchangeable. You see how easy it is to use Delegates in Unreal Engine with different types and combinations of parameters. Delegates offer you tremendous flexibility. They allow you to encapsulate specific functions and then activate them when needed. This is particularly useful when you want to respond to events or state changes with certain actions.

Delegate with Multiple Parameters:

For Delegates with more than two parameters, you use the macro DECLARE_DELEGATE_<Num>Params, where <Num> is the number of parameters. Here's an example with three parameters. Everything else remains as usual:

// Declaration of a Delegate with three parameters
DECLARE_DELEGATE_ThreeParams(MyThreeParamsDelegate, int, FString, bool);

// Example of a function that corresponds to the Delegate
void MyThreeParamsFunction(int Number, const FString& Text, bool Flag) {
    // Implementation of the functionality with UE_LOG
    UE_LOG(LogTemp, Log, TEXT("Number: %d, Text: %s, Flag: %s"), Number, *Text, Flag ? TEXT("True") : TEXT("False"));
}

// Using the Delegate:
MyThreeParamsDelegate MyThreeParamsDelegateInstance; // Definition
MyThreeParamsDelegateInstance.BindStatic(&MyThreeParamsFunction); // Binding a function
MyThreeParamsDelegateInstance.ExecuteIfBound(42, TEXT("Three Parameters"), true); // Executing the bound function

Delegates with Return Values:

In Unreal Engine, you can define Delegates that accept input parameters and also return values.

For instance, you can use the DECLARE_DELEGATE_RetVal macro to define a Delegate that expects no parameters but delivers a return value. The first parameter of the macro in this case is the expected return value of the function that can be bound to the Delegate.

Here's an example of how to implement a function with a float return value using the macro:

// Declaration of the Delegate with a return value
DECLARE_DELEGATE_RetVal(float, MyRetValDelegate);

// Example of a function that corresponds to the Delegate
float MyRetValFunction() {
    // Implementation of the functionality
    return 3.14f;
}

// Using the Delegate:
MyRetValDelegate MyDelegate; // Definition
MyDelegate.BindStatic(&MyRetValFunction); // Binding a function

float Result = MyDelegate.Execute(); // Executing the bound function and saving the return value
UE_LOG(LogTemp, Log, TEXT("Return value: %f"), Result);

Delegate with One Parameter and Return Value:

You can also combine this with the expected parameters described above. Of course, I have some examples for that too:

// Declaration of the Delegate with one parameter and a return value
DECLARE_DELEGATE_RetVal_OneParam(bool, MyRetValOneParamDelegate, FString);

// Example of a function that corresponds to the Delegate
bool MyRetValOneParamFunction(const FString& Text) {
    // Implementation of the functionality
    return Text.Equals(TEXT("Unreal"));
}

// Using the Delegate:
MyRetValOneParamDelegate MyDelegate; // Definition
MyDelegate.BindStatic(&MyRetValOneParamFunction); // Binding a function

bool IsUnreal = MyDelegate.Execute(TEXT("Unreal")); // Executing the bound function
UE_LOG(LogTemp, Log, TEXT("Is 'Unreal': %s"), IsUnreal ? TEXT("True") : TEXT("False"));

Delegate with Multiple Parameters and Return Value:

// Declaration of a Delegate with two parameters and a return value
DECLARE_DELEGATE_RetVal_TwoParams(int, MyRetValTwoParamsDelegate, float, FString);

// Example of a function that corresponds to the Delegate
int MyRetValTwoParamsFunction(float Number, const FString& Text) {
    // Implementation of the functionality
    return FMath::RoundToInt(Number) + Text.Len();
}

// Using the Delegate:
MyRetValTwoParamsDelegate MyDelegate; // Definition
MyDelegate.BindStatic(&MyRetValTwoParamsFunction); // Binding a function

int Sum = MyDelegate.Execute(3.14f, TEXT("Unreal")); // Executing the bound function
UE_LOG(LogTemp, Log, TEXT("Sum: %d"), Sum);

In these examples, you see how you can define Delegates in Unreal Engine that deliver return values. This can be implemented for all kinds of return values and any number of parameters.

How to Use Multicast Delegates?

So far, I've only explained what happens when you bind just one function to a Delegate. But what if you want a Delegate to which you can bind multiple functions that are then all executed simultaneously?

This is where Multicast Delegates come in. This type of Delegate will call more than one function when executed. Here's an example:

// Declaration of a Multicast Delegate
DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);

// Example functions corresponding to the Delegate
void MyFunction1() {
    UE_LOG(LogTemp, Log, TEXT("MyFunction1 called"));
}

void MyFunction2() {
    UE_LOG(LogTemp, Log, TEXT("MyFunction2 called"));
}

// Using the Delegate:
FMyMulticastDelegate MyMulticastDelegate;

// Binding functions
MyMulticastDelegate.Add(MyFunction1);
MyMulticastDelegate.Add(MyFunction2);

// Executing all bound functions
MyMulticastDelegate.Broadcast();

Multicast Delegates with Multiple Parameters:

Multicast Delegates can also be configured to accept functions with multiple parameters. This increases flexibility and allows for more comprehensive interaction between different systems. Here's an example of how you can define and use a Multicast Delegate with two parameters:

// Declaration of a Multicast Delegate with two parameters
DECLARE_MULTICAST_DELEGATE_TwoParams(FMyMulticastDelegateTwoParams, int, FString);

// Example functions
void MyFunction1(int Num, const FString& Text) {
    UE_LOG(LogTemp, Log, TEXT("MyFunction1: Num=%d, Text=%s"), Num, *Text);
}

void MyFunction2(int Num, const FString& Text) {
    UE_LOG(LogTemp, Log, TEXT("MyFunction2: Num=%d, Text=%s"), Num, *Text);
}

// Using the Delegate:
FMyMulticastDelegateTwoParams MyDelegate;

// Binding functions
MyDelegate.AddStatic(&MyFunction1);
MyDelegate.AddStatic(&MyFunction2);

// Executing all bound functions with parameters
MyDelegate.Broadcast(42, TEXT("Hello Unreal"));

For an extensive explanation of Multicast Delegates, I recommend checking out the Unreal Engine Documentation on the topic. Essentially, the functionality is similar to single-function Delegates, with the key difference being that multiple functions are called.

Return Values in Multicast Delegates:

Multicast Delegates in Unreal Engine do not support return values. If a function is bound to a Multicast Delegate, its return value is ignored. The reason for this lies in the nature of Multicast Delegates, as they are intended for events where the responses (bound functions) are parallel and independent of each other. Return values would not make clear sense in this context, as it would be indeterminate which return value is "correct" or relevant when multiple functions are called simultaneously.

Key Points:

  • No Return Values: Multicast Delegates cannot receive return values from their bound functions.
  • Event Handling Focus: The focus is on notifying about events or state changes, not on collecting data from callback functions.

Use Case Examples:

  • User Interface Interactions: For example, a Multicast Delegate could be used to update various UI elements when the game state changes.
  • Gameplay Events: For instance, a Multicast Delegate might be used to inform various systems when a player completes a level.

In summary, Multicast Delegates with multiple parameters offer an expanded capability to implement more complex interactions and notifications in Unreal Engine. Their inability to handle return values underscores their use as a tool for event-driven programming.

How to Bind Different Types of Functions to Delegates?

Delegates in Unreal Engine are extremely versatile when it comes to binding functions. You can bind static functions, member functions of object instances, lambda functions, and even functions from UObject-based classes. First, here's a listing of possible functions from the Official Unreal Engine 5 Documentation:

Function Description
Bind Binds to an existing delegate object.
BindStatic Binds a raw C++ pointer global function delegate.
BindRaw Binds a raw C++ pointer delegate. Since raw pointers do not use any sort of reference, calling Execute or ExecuteIfBound after deleting the target object is unsafe.
BindLambda Binds a functor. This is generally used for lambda functions.
BindSP Binds a shared pointer-based member function delegate. Shared pointer delegates keep a weak reference to your object. You can use ExecuteIfBound to call them.
BindUObject Binds a UObject member function delegate. UObject delegates keep a weak reference to the UObject you target. You can use ExecuteIfBound to call them.
UnBind Unbinds this delegate.

The different binding functions in Unreal Engine each offer specific methods for linking Delegates with functions or methods. The choice of the right binding method depends on the type of function or method you want to bind and what properties (like lifespan management and safety) are important for your application.

Here's a more detailed explanation of the various functions:

  • Bind binds a Delegate to an existing Delegate object. This method is less commonly used but is useful if you want to use a Delegate as a wrapper or proxy for another Delegate.
  • BindStatic binds a Delegate to a global function or a static class function. This method is ideal if you want to bind a function that does not require access to a class instance.
  • BindRaw binds a Delegate to a function or method via a raw C++ pointer. This is useful for classes that do not inherit from UObject and where automatic lifespan management is not required. It is important to note that using BindRaw can be unsafe if the target object is deleted since the Delegate then points to invalid memory.
  • BindLambda binds a Delegate to a functor, which is usually a lambda function. This is a very flexible method to bind custom logic directly to the Delegate without having to define separate functions.
  • BindSP binds a Delegate to a member function that is called via a Shared Pointer. This method is safer than BindRaw as the Delegate keeps a weak reference to the object and checks if the object still exists before it is called.
  • BindUObject binds a Delegate to a member function of a UObject. Similar to BindSP, the Delegate holds a weak reference to the UObject and checks its existence before the call.
  • UnBind unbinds a Delegate. This is important to ensure that the Delegate does not refer to functions or methods that are no longer valid.

In practice, you choose the binding method based on the type of function you want to bind and the requirements of your application. For UObject-based classes, BindUObject is often the best choice, while BindRaw or BindSP can be used for non-UObject classes. BindLambda offers maximum flexibility and is often used for temporary or one-time functionalities. UnBind is essential to ensure that the Delegate does not retain outdated references.

Binding Static Global Functions

As I've frequently used above, binding a static function to a Delegate is quite straightforward:

// Declaration of the Delegate
DECLARE_DELEGATE(FMyStaticFunctionDelegate);

// Static function
static void MyStaticFunction() {
    UE_LOG(LogTemp, Log, TEXT("MyStaticFunction called"));
}

// Using the Delegate:
FMyStaticFunctionDelegate MyDelegate;
MyDelegate.BindStatic(&MyStaticFunction);
MyDelegate.ExecuteIfBound();

Binding Static Member Functions

Static member functions are those associated with a class but can be called independently of a specific instance of that class. Binding a static member function to a Delegate works similarly to binding a regular static function, with the difference that the function is defined within the class:

// Declaration of the Delegate
DECLARE_DELEGATE(FMyStaticMemberFunctionDelegate);

// Class with a static member function
class MyClass {
public:
    static void MyStaticMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyStaticMemberFunction called"));
    }
};

// Using the class
int main() {
    // Using the Delegate:
    FMyStaticMemberFunctionDelegate MyDelegate;
    MyDelegate.BindStatic(&MyClass::MyStaticMemberFunction);
    MyDelegate.ExecuteIfBound();
}

In this example, MyStaticMemberFunction is a static function defined within the MyClass. It is bound to the Delegate MyDelegate and can then be called without needing an instance of MyClass.

Binding Member Functions

Member functions are those that belong to a specific instance of a class. For classes that inherit from UObject, there are special functions BindUObject for binding the functions. However, if the class does not derive from UObject, BindRaw is used instead.

To bind a member function, you need an instance of the object:

// Declaration of the Delegate
DECLARE_DELEGATE(FMyMemberFunctionDelegate);

// Class with a member function
class MyClass {
public:
    void MyMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyMemberFunction called"));
    }
};

// Using the class
int main() {
    // Using the Delegate:
    FMyMemberFunctionDelegate MyDelegate;
    
    MyClass MyObject;
    MyDelegate.BindRaw(&MyObject, &MyClass::MyMemberFunction);
    MyDelegate.ExecuteIfBound();
}

In this example, BindRaw is used to bind MyMemberFunction of an instance of MyClass to the Delegate MyDelegate. Note that MyClass does not inherit from UObject, so BindRaw is the correct choice for binding the function.

Here, in addition to the function being bound, you also provide the instance of the class whose member will be called. It's more common for the Delegate to be held as part of the class. So, if you define a Delegate within the class and bind a function to it, you can also use a this pointer for this purpose:

// Declaration of the delegate
DECLARE_DELEGATE(FMyMemberFunctionDelegate);

// Class with a member function and a delegate
class MyClass {
public:
    FMyMemberFunctionDelegate MyDelegate;

    MyClass() {
        // Here 'this' is used along with BindRaw
        MyDelegate.BindRaw(this, &MyClass::MyMemberFunction);
    }

    void MyMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyMemberFunction called"));
    }

    void TriggerDelegate() {
        // Execute the delegate if it's bound
        MyDelegate.ExecuteIfBound();
    }
};

// Using the class
int main() {
    MyClass MyObject;
    MyObject.TriggerDelegate(); // Calls MyMemberFunction
}

In this example, this is used in the constructor to bind the current instance of the class with MyMemberFunction to MyDelegate. Note that you must be particularly mindful of the lifespan of the MyClass object to ensure it isn't destroyed before the Delegate is called, as BindRaw does not provide safety regarding the object's lifespan.

To conclude, let's discuss how to use BindSP in this context. As explained earlier, the first parameter of BindRaw must be a pointer to an instance of the class. Unreal also allows passing a Shared Pointer instead of a Raw Pointer. For this, the BindSP function is used. Here's an example of its use:

#include "SharedPointer.h" // For TSharedPtr

// Declaration of the Delegate
DECLARE_DELEGATE(FMyMemberFunctionDelegate);

// Class with a member function
class MyNonUObjectClass {
public:
    void MyMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyMemberFunction called"));
    }
};

// Main function or a similar context
int main() {
    // Creating a Shared Pointer for the class
    TSharedPtr<MyNonUObjectClass> MyObject = MakeShared<MyNonUObjectClass>();

    // Creating the Delegate
    FMyMemberFunctionDelegate MyDelegate;

    // Binding the member function to the Delegate
    MyDelegate.BindSP(MyObject, &MyNonUObjectClass::MyMemberFunction);

    // Executing the Delegate
    MyDelegate.ExecuteIfBound();

    return 0;
}

In this example, we create a Shared Pointer for MyNonUObjectClass and then bind the MyMemberFunction method to MyDelegate. Since we use BindSP, the Delegate holds a weak reference to MyObject. This ensures that MyObject is managed correctly and helps to prevent accesses to deleted memory.

Binding UObject Methods:

BindUObject is used to bind functions from UObject-based classes, which derive from UObject. This is common in Unreal Engine, especially for classes integrated into gameplay logic and the actor system. It's particularly useful in situations where the delegate needs to respond to an object that is part of Unreal Engine's Garbage Collection:

// Declaration of the delegate
DECLARE_DELEGATE(FMyUObjectDelegate);

// UObject-based class
class UMyUObjectClass : public UObject {
public:
    void MyUObjectFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyUObjectFunction called"));
    }
};

// Using the class
int main() {
    // Using the delegate:
    FMyUObjectDelegate MyDelegate;
    
    UMyUObjectClass* MyUObject = NewObject<UMyUObjectClass>();
    MyDelegate.BindUObject(MyUObject, &UMyUObjectClass::MyUObjectFunction);
    MyDelegate.ExecuteIfBound();
}

When you use BindUObject within a class to bind a member function of that class, you use the this pointer to refer to the current instance of the class. This is particularly useful when the class inherits from UObject and you want to bind one of its functions to a delegate. Here's an example:

// Declaration of the delegate
DECLARE_DELEGATE(FMyUObjectDelegate);

// UObject-based class
class UMyUObjectClass : public UObject {
public:
    FMyUObjectDelegate MyDelegate;

    UMyUObjectClass() {
        // Binding a member function to the delegate with 'this'
        MyDelegate.BindUObject(this, &UMyUObjectClass::MyUObjectFunction);
    }

    void MyUObjectFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyUObjectFunction called"));
    }

    void TriggerDelegate() {
        // Execute the delegate if it's bound
        MyDelegate.ExecuteIfBound();
    }
};

// Using the class
int main() {
    UMyUObjectClass* MyUObject = NewObject<UMyUObjectClass>();
    MyUObject->TriggerDelegate(); // Calls MyUObjectFunction
}

Binding UObject Methods Using BindUFunction:

For completeness, I'd like to introduce a method that I strongly advise against, which I initially used when I had absolutely no idea what I was doing and thus took the path of least resistance.

// Declaration of the delegate
DECLARE_DELEGATE(FMyUObjectDelegate);

// UObject-based class
class UMyUObjectClass : public UObject {
public:
    FMyUObjectDelegate MyDelegate;

    UMyUObjectClass() {
        // Binding a member function to the delegate with 'this'
        MyDelegate.BindUFunction(this, FName("MyUObjectFunction"));
    }

    void MyUObjectFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyUObjectFunction called"));
    }

    void TriggerDelegate() {
        // Execute the delegate if it's bound
        MyDelegate.ExecuteIfBound();
    }
};

// Using the class
int main() {
    UMyUObjectClass* MyUObject = NewObject<UMyUObjectClass>();
    MyUObject->TriggerDelegate(); // Calls MyUObjectFunction
}

With BindUFunction, you bind the function MyUObjectFunction to MyDelegate. It seems simple and the code compiles without issues, but the catch is that you pass the function name as a FName string. This means if you mistype or later rename the function and forget to update the string, you'll get a runtime error.

Although BindUFunction looks super easy, it's deceptive. I recommend sticking to safer methods like BindUObject, which are checked at compile time. Learn from my mistakes and save yourself the hassle!

Binding Lambda Functions

You can bind lambda functions directly to delegates using the BindLambda method. Here's an example:

// Declaration of the delegate
DECLARE_DELEGATE(FMyLambdaDelegate);

int main() {
    // Using the delegate with a lambda function
    FMyLambdaDelegate MyDelegate;
    MyDelegate.BindLambda([]() {
        UE_LOG(LogTemp, Log, TEXT("Lambda function called"));
    });
    
    // Executing the delegate
    MyDelegate.ExecuteIfBound();
}

In this example, a simple lambda function is defined and bound to MyDelegate. The lambda function outputs a message to the console when the delegate is executed.

Binding Lambda Functions with Capture

Lambda functions can capture local variables or class members to use them within the lambda function. This allows you to bring state or contextual information into the lambda function. For example:

int main() {
    int Count = 42;
    FString Message = TEXT("Hello World");
    
    FMyLambdaDelegate MyDelegate;
    MyDelegate.BindLambda([Count, Message]() {
        UE_LOG(LogTemp, Log, TEXT("Count: %d, Message: %s"), Count, *Message);
    });
    
    MyDelegate.ExecuteIfBound();
}

Here, the lambda function captures the local variables Count and Message and uses them in its code.

Be careful when capturing references in lambda functions. Ensure that all captured objects or variables are valid when the delegate is called. Capturing references can lead to unwanted side effects if the lifetime of the captured object is not adequately controlled.

Binding Class Methods with Lambda

You can use a lambda function to bind a class method to a delegate by capturing the this pointer or an instance of the class. Here's an example:

// Declaration of the delegate
DECLARE_DELEGATE(FMyClassMethodDelegate);

// Class with a member function
class MyClass {
public:
    void MyMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyMemberFunction called"));
    }
};

int main() {
    // Using the delegate:
    FMyClassMethodDelegate MyDelegate;
    
    MyClass MyObject;
    MyDelegate.BindLambda([&MyObject]() {
        MyObject.MyMemberFunction();
    });
    
    MyDelegate.ExecuteIfBound();
}

Binding a Method Within the Class

If you define a delegate within the class itself and want to bind a method of the class to it, you can use the this pointer:

// Class with a member function and a delegate
class MyClass {
public:
    FMyClassMethodDelegate MyDelegate;

    MyClass() {
        // Here 'this' is used to refer to the current instance
        MyDelegate.BindLambda([this]() {
            this->MyMemberFunction();
        });
    }

    void MyMemberFunction() {
        UE_LOG(LogTemp, Log, TEXT("MyMemberFunction called"));
    }

    void TriggerDelegate() {
        // Execute the delegate if it's bound
        MyDelegate.ExecuteIfBound();
    }
};

// Using the class
int main() {
    MyClass MyObject;
    MyObject.TriggerDelegate(); // Calls MyMemberFunction
}

In this case, this is used in the lambda function to refer to the current instance of the class and call its method MyMemberFunction.

Unsubscribing Functions and Methods from Delegates

UnBind is used to remove a previously bound function or method from a delegate. This prevents the delegate from continuing to reference this function or method.

Correctly unsubscribing (UnBind) functions and methods from delegates is crucial to ensure that no unwanted calls or accesses to already destroyed objects occur. This is especially important when the lifespan of objects that delegates refer to is uncertain.

So, make sure to call UnBind or Clear before the object containing the bound function is destroyed. This is particularly important for delegates that bind methods of object instances.

UnBind a Static or Member Function

If you want to unsubscribe a static or member function from a delegate, you can use UnBind or more specific methods like Clear:

// Assuming we have a delegate that was previously bound
FMyStaticFunctionDelegate MyDelegate;

// To unsubscribe the delegate, use UnBind
MyDelegate.Unbind();

For Multicast Delegates, which can bind multiple functions, you would use Clear to remove all bindings:

DECLARE_MULTICAST_DELEGATE(FMyMulticastDelegate);
FMyMulticastDelegate MyMulticastDelegate;

// To remove all bound functions
MyMulticastDelegate.Clear();

Ensuring proper management of delegate bindings and unbindings helps maintain robust and error-free code, particularly in environments like Unreal Engine where object lifecycles and delegate interactions can be complex.

Final Words

I hope this article has helped you understand delegates and how to use them in your project. Delegates are super handy, but initially a bit tricky to apply, and the documentation on the topic is somewhat sparse or hard to understand. I hope this article contributes to the understanding, creating a small reference work you can always turn to when you need to bind a function to a delegate again.

Profile picture

Artur Schütz

Senior Software Developer specializing in C++ graphics and game programming. With a background as a Full Stack Developer, Artur has now shifted his focus entirely to game development. In addition to his primary work, Artur explores the field of Machine Learning, experimenting with ways to enrich gameplay through its integration.