「委托」是一种泛型但类型安全的方式,可在 C++ 对象上调用成员函数。可使用委托动态绑定到任意对象的成员函数,之后在该对象上调用函数,即使调用程序不知对象类型也可进行操作。——虚幻官方文档
委托本质上是基于「观察者模式」的,也就是有「订阅」的行为,在委托中称为「绑定」。
要使用委托,首先要用 DECLARE* 宏进行特定委托的类型声明,之后才允许用 Bind*()/Add*() 函数将「订阅者」(一个可调用对象)绑定到委托对象上。这样一旦某些事件触发,就可以通过 Execute()/BroadCast() 通知这些订阅者。
委托的基本类型有三种。
- 单播委托
- 多播委托
- 动态委托
单播委托 & 动态单播委托
单播,顾名思义就是最多只能绑定一个可调用对象,并且不支持反射和序列化。优点是支持返回值。
而「动态」的意思是会在执行时实时根据给定的「函数名」去查找对应的函数(需要用 UFUNCTION() 修饰),因此执行速度很慢。此外,动态单播并不支持反射和序列化。
1/**
2 * 声明一个名为 DelegateType 的委托类型
3 * 允许绑定的函数类型为 void(*)(ParamType1, ParamType2, ...)
4 * 相当于 using DelegateType = TDelegate<void(ParamType1, ParamType2, ...)>;
5 * 无返回值
6 */
7#define DECLARE_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
8
9/**
10 * 声明动态委托类型
11 * 允许绑定的函数类型为 void(*)(ParamType1 ParamName1, ...)
12 * 相当于定义了一个继承自 TBaseDynamicDelegate 的类
13 * 无返回值
14 */
15#define DECLARE_DYNAMIC_DELEGATE*(DelegateType, ParamType1, ParamName1, ...)
16
17/**
18 * 允许绑定的函数类型为 RetValType(*)(ParamType1, ParamType2, ...)
19 * 相当于 using DelegateType = TDelegate<RetValType(ParamType1, ParamType2, ...)>;
20 * 有返回值
21 */
22#define DECLARE_DELEGATE_RetVal*(RetValType, DelegateType, ParamType1, ParamType2, ...)
23
24/**
25 * 允许绑定的函数类型为 RetValType(*)(ParamType1 ParamName1, ...)
26 * 有返回值
27 */
28#define DECLARE_DYNAMIC_DELEGATE_RetVal*(DelegateType, ParamType1, ParamName1, ...)
多播委托 & 动态多播委托
多播则是允许绑定多个可调用对象,但不支持返回值和反射。
动态多播同样需要用 UFUNCTION() 修饰,但支持反射以及序列化,也就是可以在蓝图中进行绑定,此时需要使用 BlueprintAssignable 修饰符。
1/**
2 * 多播委托类型
3 * 相当于 using DelegateType = TMulticastDelegate<void(ParamType1, ParamType2, ...)>;
4 */
5#define DECLARE_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
6
7/**
8 * 线程安全多播类型
9 * 相当于 using DelegateType = TMulticastDelegate<RetValType(ParamType1, ParamType2, ...), FDefaultTSDelegateUserPolicy>;
10 */
11#define DECLARE_TS_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamType2, ...)
12
13/**
14 * 动态多播类型
15 * 相当于定义了一个继承自 TBaseDynamicMulticastDelegate 的类
16 */
17#define DECLARE_DYNAMIC_MULTICAST_DELEGATE*(DelegateType, ParamType1, ParamName1, ...)
事件
除了上面 7 个宏族,UE 里还有一种声明方式是 DECLARE_EVENT*()。它和多播很像,但指定了 Owner,即只能在 Owner 类的成员函数中调用 BroadCast()。
1#define FUNC_DECLARE_EVENT( OwningType, EventName, ReturnType, ... ) \
2 class EventName : public TMulticastDelegate<ReturnType(__VA_ARGS__)> \
3 { \
4 friend class OwningType; \
5 };
可以看到用了友元类来进行访问权限的控制。
绑定 & 解绑
非动态委托
Bind*() 系用于单播绑定,Add*() 系用于多播绑定。
1/* 绑定类的静态函数 */
2MyUnicastDelegate.BindStatic(/* A static function pointer */, /* Parameters */);
3MyMulticastDelegate.AddStatic(/* &MyClass::MyStaticFunc */, /* ... */);
4
5
6/* 绑定 lambda 函数,可能出现悬垂引用的问题 */
7BindLambda(/* Lambda */, /* Parameters */);
8AddLambda(/* [this](...) {...} */, /* ... */);
9
10
11/* 绑定 Weak lambda 函数,区别在于如果 this 无效不会执行 lambda */
12BindWeakLambda(/* User Object */, /* Lambda */, /* Parameters */);
13AddWeakLambda(/* this */, /* [this](...) {...} */, /* ... */);
14
15
16/* 绑定到继承自 UObject 对象上的某个函数 */
17BindUObject(/* UObject* */, /* A function pointer */, /* Parameters */);
18AddUObject(/* this */, /* &MyClass::MyFunc */, /* ... */);
19
20
21/* 绑定到不继承自 UObject 的对象上的函数,同样可能出现悬垂引用的问题 */
22BindRaw(/* Raw Pointer */, /* A function pointer */, /* Parameters */);
23AddRaw(/* Other */, /* &OtherClass::OtherFunc */, /* ... */);
24
25
26/* 在上面的基础上,传入对象为 Shared Pointer */
27BindSP(/* Shared Pointer */, /* A function pointer */, /* Parameters */);
28AddSP(/* Other.ToSharedRef() */, /* &OtherClass::OtherFunc */, /* ... */);
29
30
31/* 上面的线程安全版本 */
32BindThreadSafeSP(...);
33AddThreadSafeSP(...);
34
35
36/* 基于函数名进行绑定,需要用 UFUNCTION() 修饰 !!开销很大!! */
37BindUFunction(/* User Object */, /* Function name */, /* Parameters */);
38AddUFunction(/* this */, /* "MyFunction" */, /* ... */);
39
40
41/* 单播/动态单播 解除绑定 */
42UnBind();
43
44
45/* 多播/动态多播 解除某个绑定 */
46Remove(/* this */, /* &MyClass::MyFunc */);
47Remove(/* Delegate Handle */);
48
49
50/* 多播/动态多播 解除所有绑定 */
51RemoveAll();
52Clear();
对于单播而言,后续的绑定会覆盖之前的。
动态委托
动态委托绑定的函数必须用 UFUNCTION() 修饰,否则无效。
1/* 动态单播 */
2#define BindDynamic( UserObject, FuncName ) \
3 __Internal_BindDynamic( UserObject, FuncName, \
4 STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
5
6BindDynamic(/* User Object */, /* A function poiter */);
7
8
9/* 动态多播 */
10#define AddDynamic( UserObject, FuncName ) \
11 __Internal_AddDynamic( UserObject, FuncName, \
12 STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
13
14AddDynamic(/* this */, /* &MyClass::MyFunc */);
15
16
17/* 动态多播 且去重 */
18#define AddUniqueDynamic( UserObject, FuncName ) \
19 __Internal_AddUniqueDynamic( UserObject, FuncName, \
20 STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
21
22AddUniqueDynamic(...);
23
24
25/* 移除 */
26#define RemoveDynamic( UserObject, FuncName ) \
27 __Internal_RemoveDynamic( UserObject, FuncName, \
28 STATIC_FUNCTION_FNAME( TEXT( #FuncName ) ) )
29
30RemoveDynamic(...);
这几个其实都是宏定义,而不是类的成员函数(所以 intellisense 没法识别)。
通知
1/* 单播/动态单播 通知,可能存在函数指针无效的问题,此时会报错 */
2Execute(/* Parameters */);
3
4
5/* 上面的安全版本,如果函数指针无效则不执行 */
6ExecuteIfBound(/* Parameters */);
7
8
9/* 多播/动态多播 通知所有订阅者 */
10BroadCast(/* Parameters */);
注意 Execute*() 都可以有返回值,但 BroadCast() 不行。