🔔

【UE5】デリゲートの登録と実行を分離する (UE5.5 -)

に公開

はじめに

Unreal Engine では、DECLARE_DELEGATEDECLARE_MULTICAST_DELEGATEなどのマクロを使って、簡単にイベント通知の仕組み (デリゲート) を実装することができます。
これらのデリゲートはBind〇〇()/Unbind()Add〇〇()/Remove()などを使って関数の登録/解除を行い、Execute()Broadcast()で実行できる便利な仕組みです。UIイベントや状態通知など、様々な場面で活用されていると思います。

public:
    DECLARE_DELEGATE(FSampleDelegate, int32);
    FSampleDelegate SampleDelegate;

protected:
    void Example()
    {
        // デリゲートに関数を登録
        SampleDelegate.AddLambda(
            [](int32 Value)
            {
                UE_Log(LogTemp, Log, TEXT("executed! Value=%d"), Value);
            });

         // デリゲートに登録された関数を実行
         SampleDelegate.ExecuteIfBound(65535);
    }

しかし、デリゲートを外部から登録可能にするために public なメンバとして公開してしまうと、意図せず外部から実行されてしまうリスクがあります。
「外部から登録してほしいけど、実行はしてほしくない」というケースにおいて、この点は大きな懸念事項でした。

この問題を解決するため、UE5.5 から TDelegateRegistration および TMulticastDelegateRegistration というクラスが導入されました。
これらを使うことで、外部に対して関数の登録のみを許可し、実行は制限する といった安全な設計が可能になります。

TDelegateRegistration/TMulticastDelegateRegistration

TDelegateRegistration/TMulticastDelegateRegistration は、DECLARE_DELEGATEなどで定義されるデリゲート型の基底クラスとして、 UE5.5 から導入されたクラスです。
これらは、従来のデリゲート機能から 関数の登録 のみを切り出したインターフェースとなっており、実行 (Execute/Broadcast) などの機能は持っていません。
これにより、デリゲートを外部に公開する際に「登録だけ許可して実行は許さない」といった意図的なアクセス制御が可能となりました。

これらの型は、デリゲートの引数の型をテンプレート引数に持つテンプレートクラスとして定義されています。
つまり TDelegateRegistration らの型を得たい場合は、面倒なテンプレート指定をする必要があります。
そこで便利なのが、デリゲート型に定義されているRegistrationTypeという型エイリアスです。
これをを使うことで、テンプレート引数を明示することなく、登録専用の型を簡単に参照することができます。

public:
    DECLARE_DELEGATE(FSampleDelegate, int32);
    FSampleDelegate SampleDelegate;

protected:
    void Example()
    {
        // RegistrationType エイリアスを参照すれば, デリゲートの引数の型を気にせず利用できる
        FSampleDelegate::RegistrationType& SampleDelegateRegister = SampleDelegate;

        SampleDelegateRegister.AddLambda(
            [](int32 Value)
            {
                UE_Log(LogTemp, Log, TEXT("executed! Value=%d"), Value);
            });
    }

これからのデリゲートの公開

登録用にデリゲートを外部に公開する際は、TDelegateRegistration/TMulticastDelegateRegistration 型として参照を公開すると良いでしょう。
デリゲートを「誰が実行するべきか」という責務を明確に分離でき、より堅牢な設計になります。

public:
    DECLARE_DELEGATE(FSampleDelegate, int32);

// UE5.5 以前
public:
    FSampleDelegate SampleDelegate;    // 外部からExecute()できてしまう

// UE5.5 以降
private:
    FSampleDelegate SampleDelegatePrivate;    // 外部から実行できないよう隠蔽

public:
    // 登録用に TDelegateRegistration 型で公開
    FSampleDelegate::RegistrationType& SampleDelegate()  
    {
        return SampleDelegatePrivate;
    }

Discussion