Advanced Delegates in C++

This is the second part of a short series on delegates in Unreal Engine. In the first part, we covered how to define and use a Dynamic Multicast delegate. If you are new to delegates check that out first!

In the previous tutorial, we used an example of notifying the UI when the player's score has changed. We will continue using that in this tutorial.

As before, will go through the four steps needed to set up and use a delegate in Unreal, but this time we will discuss in more detail the options available and their nuances.

  1. Define the delegate's signature: Just like a function, what parameters will your delegate have? Will it have a return type?
  2. Create variables of your new delegate: These are instances of your delegate that other functions can subscribe to.
  3. Subscribe to the delegate: You will need to connect any functions that you wish to be called when the delegate is called.
  4. Execute the delegate: Any functions that subscribed are called.

1. Declaring a Delegate

Choosing a Delegate Type

In the previous tutorial we just used a Dynamic Multicast Delegate, but Unreal actually has four(-ish) different types of delegate:

  • Single
  • Multicast
  • Dynamic single
  • Dynamic multicast

Which one you pick depends on what you want to be able to do with your delegate. Look at this table to see which one is appropriate for you.

  Single Multicast Dynamic Single Dynamic Multicast
How many can subscribe? One Many One Many
Can use from Blueprints? :x: :x: :heavy_check_mark: :heavy_check_mark:
Performance - - Slightly slower? Slightly slower?

In the next section, take note that the syntax for declaring dynamic and non-dynamic delegates is different.

Delegate Signature Tool

The signature for different delegate types is is pretty different. To see how it changes, try playing around with the tool below, changing the delegate type, adding or removing parameters, changing return types, and see how delegate declaration signature changes.

// Delegate signature

(none)

// Function signature

(none)

Non-dynamic Delegate Syntax

Non-dynamic delegate declarations start with the macro, then the name of the delegate signature that you are defining. The Unreal Engine codebase uses the prefix F with delegate signatures. I like to add a Signature suffix to differentiate between the delegate signature and delegate instance variables.

// It's possible to declare a delegate with no params
DECLARE_DELEGATE(FOnScoreChangedSignature);
// Matching function: void OnScoreChanged();

// Note that we do not need the parameter name, but it is good practice to add it
DECLARE_DELEGATE_OneParam(FOnScoreChangedWithScoreSignature, int32 /* NewScore */);
// Matching function: void OnScoreChangedWithScore(int32 NewScore);

// Note "params" is plural
DECLARE_DELEGATE_TwoParams(FOnScoreChangedWithOwnerSignature, int32 /* NewScore */, class APlayerState* /* OwningPlayer */);
// Matching function: void OnScoreChangedWithOwner(int32 NewScore, class APlayerState* OwningPlayer);

// You can split declarations over multiple lines with backslash
// Note "params" is plural
DECLARE_DELEGATE_TwoParams(FOnMultilineExampleSignature, \
	int32 /* NewScore */,\
	class APlayerState* /* OwningPlayer */);
// Matching function: void OnScoreChangedWithOwner(int32 NewScore, class APlayerState* OwningPlayer);

Dynamic Delegate Syntax

// It's possible to declare a dynamic delegate with no params
DECLARE_DYNAMIC_DELEGATE(FOnScoreChangedSignature);
// Matching function: void OnScoreChanged();

// Note we need a comma between the parameter type and the parameter name
DECLARE_DYNAMIC_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);
// Matching function: void OnScoreChanged(int32 NewScore);

// Note "params" is plural
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnScoreChangedSignature, int32, NewScore, class APlayerState*, OwningPlayer);
// Matching function: void OnScoreChanged(int32 NewScore, APlayerState* OwningPlayer);

Return Values

Non-multicast delegates can also include a custom return value, instead of the default void. Simply add RetVal and insert the return type at the start of the macro.

DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnDogSucceededWoofing, class ADog* /* Dog */, FString /* WoofWord */);
// Matching function: bool OnDogWoof(ADog* Dog, FString WoofWord);

DECLARE_DYNAMIC_DELEGATE_RetVal_TwoParams(bool, FOnDogSucceededWoofing, class ADog*, Dog, FString, WoofWord);
// Matching function: bool OnDogWoof(ADog* Dog, FString WoofWord);

Multicast Delegates

The signature for multicast delegates is exactly the same, just add MULTICAST_ before DELEGATE. The signature of the functions that can bind to them are the same. However the way to bind to them is a little different, as we will see later.

Note that return values are not supported for multicast delegates.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);

2. Creating Variables of Delegate Type

We've chosen the type of delegate we want, and its signature which defines the parameters that they will supply to functions that they call. We now need to add these delegates to a class or struct somewhere, so others that want to be notified can subscribe to them.

No matter the type of delegate we have declared, adding it to another class is the same. However note only Dynamic delegates can be exposed through Blueprints.

BUIPlayerState.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerState.h"
#include "BUIPlayerState.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);

UCLASS()
class ABUIPlayerState : public APlayerState
{
	GENERATED_BODY()

public:
	// We want this public so our UI can access it to subscribe to it
	// Also adding BlueprintAssignable makes it accessible by blueprints
	UPROPERTY(BlueprintAssignable)
	FOnScoreChangedSignature OnScoreChangedDelegate;
};

3. Subscribe to a delegate

Now that we have declared our delegate, and created member variables of it somewhere in our codebase, we can now connect one or more functions to them, so the functions will be called when the delegate is called.

As with defining the delegate, subscribing to the delegate is different depending on whether it's single/multicast and non-dynamic/dynamic.

Non-Dynamic Single Delegate

Non-dynamic delegates have a host of different functions you can use to bind a function or lambda to be executed. I won't cover them all but here's the syntax for a few of the ones that I have used more frequently.

  • BindLambda
  • BindRaw
  • BindStatic
  • BindSP
  • BindUFUnction
  • BindUObject
  • BindWeakLambda
  • BindThreadSafeSP
DECLARE_DELEGATE_OneParam(FOnScoreChangedSignature, int32 /* NewScore */);

// BindUObject requires that the target be a UObject, and that the function
// be a UFUNCTION()
OnScoreChangedDelegate.BindUObject(this, &ThisClass::OnScoreChanged);

// BindRaw is for if the target is not a UObject
OnScoreChangedDelegate.BindRaw(SomeSlateThing, &SSlomeSlateThing::OnScoreChangedRaw);

// BindLambda is useful for simpler anonymous functions
OnScoreChangedDelegate.BindLambda([](int32 NewScore)
{
	// Do something with score
});

Non-Dynamic Multicast Delegate

Subscribing to non-dynamic multicast delegates is very much the same as their single counterparts. Functions are prefixed with Add instead of Bind, because multiple functions can be bound to the delegate.

  • AddLambda
  • AddRaw
  • AddStatic
  • AddSP
  • AddUFunction
  • AddUObject
  • AddWeakLambda
  • AddThreadSafeSP
DECLARE_MULTICAST_DELEGATE_OneParam(FOnScoreChangedSignature, int32 /* NewScore */);

// AddUObject requires that the target be a UObject, and that the function
// be a UFUNCTION()
OnScoreChangedDelegate.AddUObject(this, &ThisClass::OnScoreChanged);

// AddRaw is for if the target is not a UObject
OnScoreChangedDelegate.AddRaw(SomeSlateThing, &SSlomeSlateThing::OnScoreChangedRaw);

// AddLambda is useful for simpler anonymous functions
OnScoreChangedDelegate.AddLambda([](int32 NewScore)
{
	// Do something with score
});

Dynamic Single Delegate

DECLARE_DYNAMIC_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);

// If we have a UFUNCTION()-marked function `OnScoreChanged(int32 NewScore)
// we can subscribe using BindDynamic and the ThisClass macros
OnScoreChangedDelegate.BindDynamic(this, &ThisClass::OnScoreChanged);

Dynamic Multicast Delegate

For Dynamic Multicast Delegates I would recommend using AddUniqueDynamic, as it only binds if the supplied UFUNCTION has not been bound before.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChangedSignature, int32, NewScore);

// If we have a UFUNCTION()-marked function `OnScoreChanged(int32 NewScore)
// we can subscribe using AddUniqueDynamic and the ThisClass macros
OnScoreChangedDelegate.AddUniqueDynamic(this, &ThisClass::OnScoreChanged);

4. Call the delegate

Thankfully after all the previous work, this is relatively straightforward.

  • For single delegates: .Execute() or .ExecuteIfBound()
  • For multicast delegates: .Broadcast()

Advanced Topics

These are some more advanced or nuanced issues related to delegates.

Re-using delegate signatures

There are some situations in which many different events may share the same signature. For a kind of silly example, imagine we had delegates for different things our ADog class could do:

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

	{Delegate type} OnDogJumpedDelegate;
	{Delegate type} OnDogWoofedDelegate;
	{Delegate type} OnDogSatDownDelegate;
};

What should be in {Delegate type}? Should we declare 3 delegates with the same parameter?

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDogJumpedSignature, ADog*, Dog);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDogWoofedSignature, ADog*, Dog);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDogSatDownSignature, ADog*, Dog);

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

	FOnDogJumpedSignature OnDogJumpedDelegate;
	FOnDogWoofedSignature OnDogWoofedDelegate;
	FOnDogSatDownSignature OnDogSatDownDelegate;
};

Or just one, and re-use that same event?

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(OnDogEventSignature, ADog*, Dog);

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

	FOnDogEventSignature OnDogJumpedDelegate;
	FOnDogEventSignature OnDogWoofedDelegate;
	FOnDogEventSignature OnDogSatDownDelegate;
};

I think in this kind of silly example, the second one makes more sense but if the events really are different purposes and only by chance happen to have the same signature, then you could declare a separate delegate type for delegates with different purposes.

Sparse Delegates

Sparse Delegates are a special type of delegate that should be used for some situations:

  • When the delegate is only rarely bound.
  • When memory usage of the object containing the delegate is a consideration. For example there will be many instances of that object so reducing the per-instance size is desired.

Only only comes in the Dynamic Multicast flavour, and don't seem to support return values. You can see them in use in Actor.h and PrimitiveComponent.h, and are defined in SparseDelegate.h that contains this comment:

Sparse delegates can be used for infrequently bound dynamic delegates so that the object uses only 1 byte of storage instead of having the full overhead of the delegate invocation list. The cost to invoke, add, remove, etc. from the delegate is higher than using the delegate directly and thus the memory savings benefit should be traded off against the frequency with which you would expect the delegate to be bound.

Their macro signature is a very different to other delegates so be careful:

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE(SparseDelegateClassName, OwningClass, DelegateName);
DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(SparseDelegateClassName, OwningClass, DelegateName, Param1Type, Param1Name);

Dog.h

#pragma once

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

// Note you have to forward declare here, you *cannot* do it within the
// delegate declaration 
class ADog;

DECLARE_DYNAMIC_MULTICAST_SPARSE_DELEGATE_OneParam(FOnDogWoofSignature, ADog, OnDogWoofDelegate, FString, WoofText);

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

	// We only very rarely need to subscribe to woofs
	// And we have a *lot* of dogs
	FOnDogWoofSignature OnDogWoofDelegate;
};

There is also a handy console command SparseDelegateReport, to output a report on which sparse delegates are bound.

Using Datatypes with Commas

At one point you've probably tried to declare a delegate that uses TMap<K,V>, like this:

// This doesn't work!
DECLARE_DELEGATE_OneParam(FOnScoresForPlayersChangedSignature, TMap<FName, int32> /* ScoreMap */)

Unreal's DECLARE macro is getting confused because of the comma in TMap<FName, int32>. There are a few ways to solve this.

Use typedef

One way to solve this is to create a new typedef for your TMap<FName, int32>

typedef TMap<FName, int32> ScoreMap;
DECLARE_DELEGATE_OneParam(FOnScoresForPlayersChangedSignature, ScoreMap /* NewMap */)

Use TDelegate instead of macro

The other way is to avoid using the macro altogether, and just declare the type inline with your delegate. Note that these will only be accessible from C++, not from Blueprints.

Using TDelegate instead of DECLARE_DELEGATE

UCLASS()
class ABUIPlayerState : public APlayerState
{
	GENERATED_BODY()

public:
	// We don't need the DECLARE_DELEGATE_... macro
	TDelegate<void(TMap<FName, int32>)> OnScoreChangedDelegate;
};

Wrap in a struct

If you want to use a dynamic delegate and have it used by Blueprints, I would instead wrap that datatype in a struct and use that as the delegate parameter. It's also good practice to use a struct as a parameter when you start needing more than a few parameters or you think you may need more in the future. Wrapping all the required information in a structure avoids the signature changing and avoids having to refactor functions and delegates.

Using a struct to wrap a TMap<K,V>

USTRUCT(BlueprintType)
struct FScoreData
{
	GENERATED_BODY()

	UPROPERTY(BlueprintReadWrite)
	TMap<FName, int32> ScoreMap;
};

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnScoreChangedSignature, FScoreData, ScoreData);

UCLASS()
class ABUIPlayerState : public APlayerState
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintAssignable)
	FOnScoreChangedSignature OnScoreChangedDelegate;
};

Serialization

Only Dynamic Delegates support serialization, but why would we care about serialization?

In our running example of binding a UI widget to the Player State's OnScoreChangedDelegate, we would not normally need to serialize that binding. We could do it when the widget is created when the game starts.

However, let's instead imagine we are creating a city building game, and we have instances of an actor AWorker that run around and can bind to delegates that are executed when tasks are complete. In this case we could imagine that we might want to serialize those subscriptions so they are restored when re-loading a game.

Then when the game saves its state, we want it to preserve those connections. For that we need to use Dynamic Delegates.

Events

Events are mentioned in the 4.27 documentation but Dylan found this little gem in the 5.0 engine source code:

/**
 * Declares a multicast delegate that is meant to only be activated from OwningType
 * NOTE: This behavior is not enforced and this type should be considered deprecated for new delegates, use normal multicast instead
 */
#define DECLARE_EVENT( OwningType, EventName ) FUNC_DECLARE_EVENT( OwningType, EventName, void )

So I would consider Events deprecated as of 5.0. Just use multicast delegates instead.

Posted: