本文章只是我个人在学习虚幻智能指针过程中的理解,不一定正确,若有说的不对的地方,欢迎指正。
在学生时代,我们写程序的时,想要在一个A类中调用B类的方法函数的时候,我们一般会A类内保存一个B类的引用。通过引用来调用B类的函数,就像这样:
#include <iostream>
class B
{
public:
//目标函数
void Func()
{
printf("Call Func\n");
}
};
//A内保存对B类一个实例的引用
class A
{
public:
//目标函数所在对象
B BInstance;
};
int main()
{
//创建一个A对象
A* AInstance = new A;
//通过B实例类调用Func
AInstance->BInstance.Func();
}
但这样做会造成A类与B类的耦合(A类要知道B类的类型才能获得引用),造成某些不好的影响。而在虚幻引擎里写复杂代码的过程中为了减少类似的情况发生,也为了代码便于后期维护,引擎提供了代理。
那么什么是代理?
代理就是夹在A类和B类中间的类型,A类需要调用B类的某个函数,它可以通过保存在类内的代理对象来调用这个函数。而由于代理是通用的,只需要知道函数的返回值和参数类型就可以绑定不同的方法,A类并不需要函数类的具体消息,从而实现了解耦。
一.虚幻代理的分类
虚幻代理按照可以绑定的函数个数分为单播和多播,又按照是否暴露给蓝图分为静态和动态,最后再加一个委托类型——事件
1.单播代理:
代理和函数提供者之间是一对一的关系,调用者执行代理,只有一个函数提供者会受到通知然后执行。单播代理被重复绑定时,后一个绑定的函数会覆盖前一个函数。
2.多播代理:
代理和函数提供者之间是一对多的关系,调用者执行代理,会有多个函数提供者会受到通知然后执行。因为是一对多的关系,所以多播代理不会出现后面绑定的函数覆盖前一个函数的现象。
3.动态单播和动态多播:
通过名字可以知道,也是一对一和一对多的关系,但是这两种代理可以暴露给蓝图,绑定函数可以在蓝图中进行。
二.虚幻的代理
我们可以根据要绑定函数的返回值类型和参数的个数和类型,在虚幻引擎提供的代理中选正确的代理,虚幻提供的代理最终都是用宏来封装的(挖坑:代理的源码后面会写篇文章来讲讲)。目前虚幻的代理最多支持绑定九个参数的函数,为了方便使用,各个代理都是根据参数和返回值来命名的。
最简单的一参数静态单播代理:DECLARE_DELEGATE_OneParam,前面的DECLARE_DELEGATE是普遍使用的,后面的OneParam表示此代理可绑定携带一个参数的函数。当需要绑定有返回值的函数,只需要再在**_OneParam前加上_RetVal**即可。
静态多播则增加一个**_MULTICAST**,例如DECLARE_MULTICAST_DELEGATE_OneParam(携带一参数静态多播代理)。
若是要使用动态代理就在DECLARE和DELEGATE之间加上DYNAMIC,例如DECLARE_DYNAMIC_DELEGATE_OneParam(携带一参数动态单播代理)。
动态多播与静态多播相似,例如DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(携带一参数动态多播代理)。
现在我们来看看虚幻引擎提供的代理的用法。虚幻代理的使用大致可分为三个步骤:1.声明代理;2.绑定函数;3.执行代理,这里用DECLARE_DELEGATE_OneParam来举例。
I.代理声明:
DECLARE_DELEGATE_OneParam可以传入DelegateName和Param1Type两个宏参数。顾名思义,DelegateName是代理名,实际上就是用该宏来生成一个类型是DelegateName的类。至于Param1Type,很简单,就是参数的类型。
接着用这个类生成一个代理对象,使用对象来绑定函数。下面来看一下实际代码:
//声明一个带int32类型参数无返回值的代理
DECLARE_DELEGATE_OneParam(MyDelegateName, int32);
//生成代理对象
MyDelegateName MyDelegateInstance;
当函数有返回值时就使用DECLARE_DELEGATE_RetVal_OneParam,然后在宏参数列表的最前面多传入一个RetValType即可:
//声明一个带int32类型参数返回int32类型的代理
DECLARE_DELEGATE_RetVal_OneParam(int32, MyRetDelegateName, int32);
//生成代理对象
MyRetDelegateName MyRetDelegateInstance;
注意:动态代理声明时传入的DelegateName要在前面写一个’F’,这是虚幻引擎反射系统的要求
II.绑定函数:
1.单播代理:
单播代理绑定函数有多种方法,我们来演示一下常用的,这里先引用一张虚幻官方文档的图片:
绑定Lambda表达式:
MyDelegateInstance.BindLambda([=](int32 InElem)
{
//Do Something
});
注:Lambda可以当成是匿名函数
绑定C++原生类的函数:
//生成原生类实例
MyClass ClassInstance;
MyDelegateInstance.BindRaw(&ClassInstance, &MyClass::Func);
绑定智能指针对象函数:
绑定智能指针有两种,一种是线程安全,一种是非线程安全
//生成一个智能指针
TSharedPtr<MyClass> MyClassSP(new MyClass);
//非线程安全
MyDelegateInstance.BindSP(MyClassSP.ToSharedRef(), &MyClass::Func);
//生成一个智能指针
TSharedPtr<MyClass, ESPMode::ThreadSafe> MyClassThreadSafeSP(new MyClass);
//线程安全
MyDelegateInstance.BindThreadSafeSP(MyClassThreadSafeSP.ToSharedRef(), &MyClass::Func);
绑定静态函数:
//静态函数
static void StatFunc(int32 InElem)
{
//Do Something
}
MyDelegateInstance.BindStatic(StatFunc);
绑定UObject函数:
UCLASS()
class TESTPROJECT_API ATestProjectGameModeBase : public AGameModeBase
{
//……
//声明一个函数
void Func(int32 InElem) {}
};
MyDelegateInstance.BindUObject(this, &ATestProjectGameModeBase::Func);
注:ATestProjectGameModeBase 是继承自UObject
绑定反射函数
MyDelegateInstance.BindUFunction(this, "Func");
注:BindUFunction是通过函数名来绑定
解除绑定只需要调用一下UnBind:
MyDelegateInstance.Unbind();
2.多播代理:
多播代理绑定方法与单播的绑定一一对应,把Bind字样改成Add就好,这里就不一一列出。
这里来看看官方文档提供的图片,大概也是单播那几种:
解除多播的所有绑定,只要用代理对象调用一下RemoveAll,传入使用对象(这里用个this代替一下)即可。
MyMultDelegateInstance.RemoveAll(this);
III.执行代理
1.单播代理:
不判断直接执行,需要传入给执行函数的参数
MyDelegateInstance.Execute(321);
判断是否绑定
MyDelegateInstance.IsBound();
判断是否绑定,若已经绑定就调用Execute,需要传入给执行函数的参数
MyDelegateInstance.ExecuteIfBound(321);
2.多播代理:
直接调用Broadcast,之前绑定的所有函数都会执行,需要传入给执行函数的参数
MyMultDelegateInstance.Broadcast(321);
好了,虚幻引擎使用代理的三个步骤已经说完了,虚幻所有的代理基本都是这么使用的。剩下的动态代理和事件的使用会在《UE4 代理(Delegate)的使用(2)》里面讲,其实看完这篇,另外三个就简单了。