
在游戏开发过程中,委托是一个必不可少东西,它可以动态绑定和解绑函数,可以使我们在开发过程中不必要过多的关注我们需要调用的对象,只需要关注调用的时刻,在SDL或者Win32开发中,其中窗体应用程序基本会使用循环来处理消息通知,而在消息通知中我们在获取到通知之后,该做什么事这个部分,就可以充分的利用委托来帮我们做。
例如:
LRESULT AppWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_LBUTTONDOWN:
// 当鼠标左键点击时,需要调用的操作
A->MouseButtonDownFunc(wParam, lParam);
B->MouseButtonDownFunc(wParam, lParam);
C->MouseButtonDownFunc(wParam, lParam);
return 0;
default:
break;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
} 这种方式是没有委托的方式,十分不不友好,因为在窗体处理消息时,需要获取实例A,B,C的指针,来通过鼠标左键点击来处理A,B,C的逻辑,这使得我们每次在添加一个物体的鼠标逻辑时,都需要修改一次消息处理的代码。
LRESULT AppWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_LBUTTONDOWN:
// 当鼠标左键点击时,需要调用的操作
Delegate.BrodCast(wParam, lParam)
return 0;
default:
break;
}
return DefWindowProc(hWnd, Msg, wParam, lParam);
} 当有了委托之后,消息处理函数不必关心调用的是什么逻辑,就通过委托(Delegate)广播需要实现左键点击操作的函数,当我们要添加一个物体进行左键点击操作时,只需要将物体的函数绑定到委托上,就需要修改消息处理的逻辑。
在虚幻游戏引擎提供的功能中,有一项就是委托,我们可以方便的使用委托这个功能开发各种逻辑,但是原生C++是没有委托的,如果我们使用原生C++开发程序时,自己造一个委托的轮子,在以后开发C++程序的过程中都可以用上。
委托的本质就是一个结构体或一个类,里面封装了一些功能比如Bind(),UnBind(),包括重载了()运算符,等功能,类中的成员变量其中就有指向函数的指针,通过函数指针来调用我们绑定的函数,如果是多播委托(一个委托可以绑定多个函数),里面会有函数指针的表,遍历表里所有的函数指针来达到一次性调用多个函数的目的。
再来,我们委托绑定的函数可以为不同参数,也可能为不同的返回值,在这里我们就需要用C++模板的知识了,我们定义一个委托类模板,通过不同模板参数来实例化不同的模板类,按照这个思路,我们开始来实现吧。
先定义一个委托实现的接口:
template<typename ReturnT, typename ...ArgsT>
struct IDelegate
{
virtual ReturnT operator() (ArgsT... args) = 0;
}; 定义一个接口IDelegate模板,绑定的函数有返回值和有传入参数,所以定义模板参数ReturnT为返回值类型,...ArgsT为传入参数类型。将operator()定义为纯虚函数,继承这个接口的类必须实现operator()函数,实现Delegate的多态,因为委托可能会绑定类成员函数,也可能会绑定非类成员函数。
继承接口定义实现委托的类:
//类成员函数模板
template<typename ClassT, typename ReturnT, typename ...ArgsT>
class DynamicDelegate : public IDelegate<ReturnT, ArgsT...>
{
public:
typedef ReturnT(ClassT::* FunT) (ArgsT...);
DynamicDelegate() = delete;
explicit DynamicDelegate(ClassT* objPtr, FunT funPtr) :obj(objPtr), func(funPtr) { };
~DynamicDelegate() { };
virtual ReturnT operator() (ArgsT... args)
{
return (obj->*func)(args...);
}
ClassT* obj;
FunT func;
}; 继承IDelegate来定义类成员函数委托模板DynamicDelegate,要调用一个类成员函数,必须得有类的实例,类成员函数的指针,模板参数多了一个ClassT,ClassT为类成员函数所属于的类的类型。DynamicDelegate的成员变量为类实例的指针,类成员函数的函数指针,通过实现IDelegate接口的operator()函数,来调用类的成员函数。
//非成员函数模板
template<typename ReturnT, typename ...ArgsT>
class StaticDelegate : public IDelegate<ReturnT, ArgsT...>
{
public:
typedef ReturnT(*FunT) (ArgsT...);
StaticDelegate() = delete;
explicit StaticDelegate(FunT funPtr) :func(funPtr) { };
~StaticDelegate() { };
virtual ReturnT operator() (ArgsT... args)
{
return (*func)(args...);
}
FunT func;
}; 继承IDelegate来定义非类成员函数委托模板StaticDelegate,StaticDelegate的成员变量为非类成员函数的函数指针,通过实现IDelegate接口的operator()函数,来调用类的成员函数。
实现单播委托:
template<typename ReturnT, typename ...ArgsT>
class FunDelegate
{
public:
explicit FunDelegate();
explicit FunDelegate(typename DelegateInterface::StaticDelegate<ReturnT, ArgsT...>::FunT funPtr);
template<typename ClassT>
explicit FunDelegate(ClassT* obj, typename DelegateInterface::DynamicDelegate<ClassT, ReturnT, ArgsT...>::FunT funPtr);
~FunDelegate();
void Bind(typename DelegateInterface::StaticDelegate<ReturnT, ArgsT...>::FunT funPtr);
template<typename ClassT>
void Bind(ClassT* obj, typename DelegateInterface::DynamicDelegate<ClassT, ReturnT, ArgsT...>::FunT funPtr);
ReturnT Invoke(ArgsT... args);
ReturnT operator() (ArgsT... args);
void Clear();
private:
std::unique_ptr<DelegateInterface::IDelegate<ReturnT, ArgsT...> > dlgtPtr = nullptr;
}; 定义单播委托模板,单播委托(一个委托只能绑定一个函数),成员变量为一个IDelegate指针(我选择了用智能指针unique_ptr指向IDelegate实例,这是垃圾回收相关的问题,这里不展开讨论)。IDelegate指针可以指向DynamicDelegate 和StaticDelegate 这两个委托类,不用关心指针指向的是类成员函数委托还是非类成员函数委托,因为IDelegate::operator()是虚函数。Bind()和Clear()实现了绑定函数和解绑函数的操作,Bind()函数重载了两个版本,一个用于绑定类成员函数,一个用于绑定非类成员函数,Invoke()用来执行委托的单播,作用和operarot()一样。(函数具体实现在文末尾的链接里)
实现多播委托:
template<typename ...ArgsT>
class MultiDelegate
{
public:
MultiDelegate();
~MultiDelegate();
void AddFunc(typename DelegateInterface::StaticDelegate<void, ArgsT...>::FunT funPtr);
template<typename ClassT>
void AddFunc(ClassT* obj, typename DelegateInterface::DynamicDelegate<ClassT, void, ArgsT...>::FunT funPtr);
void BroadCast(ArgsT... args);
void operator() (ArgsT... args);
bool RemoveFunc(typename DelegateInterface::StaticDelegate<void, ArgsT...>::FunT funPtr);
template<typename ClassT>
bool RemoveFunc(ClassT* obj, typename DelegateInterface::DynamicDelegate<ClassT, void, ArgsT...>::FunT funPtr);
void Clear();
private:
std::vector<std::shared_ptr<DelegateInterface::IDelegate<void, ArgsT...> > > dlgtPtrArray;
}; 定义多播委托模板,多播委托(一个委托绑定多个函数),和单播委托不一样的地方就是,多播委托绑定多个函数时需要多个IDelegate指针,我们就用C++标准库的容器vector来存储这多个IDelegate指针。AddFunc()和RemoveFunc()实现了添加函数绑定和移除函数绑定的操作,并针对类成员函数和非类成员函数重载了两个版本,Clear()实现了对所有函数绑定的清空,BroadCast()负责遍历IDelegate指针依次调用绑定的函数,作用和operator()一样。(函数具体实现在文末尾的链接里)
添加宏定义:
#ifndef DECLARE_FUNCTION_DELEGATE
#define DECLARE_FUNCTION_DELEGATE(DelegateName, ReturnValueType, ...) typedef FunDelegate<ReturnValueType,__VA_ARGS__> (DelegateName);
#endif
#ifndef DECLARE_FUNCTION_MULTICAST_DELEGATE
#define DECLARE_FUNCTION_MULTICAST_DELEGATE(DelegateName, ...) typedef MultiDelegate<__VA_ARGS__> (DelegateName);
#endif 用过虚幻引擎的小伙伴们应该都知道,虚幻引擎无论是单播,多播,还是动态委托基本都是通过宏来声明的,我添加这个宏定义也是为了让我更方便的定义一个委托。
DECLARE_FUNCTION_DELEGATE(委托名,返回值,参数...)来定义单播委托。
DECLARE_FUNCTION_MULTICAST_DELEGATE(委托名,参数...)来定义多播委托。
#include <iostream>
#include "delegate.h"
using namespace std;
class A
{
public:
void PrintAdd(int a, int b)
{
cout << "Call PrintAdd By Class A, Value = " << a + b << endl;
}
};
class B
{
public:
void PrintAdd(int a, int b)
{
cout << "Call PrintAdd By Class B, Value = " << a + b << endl;
}
};
void PrintAdd(int a, int b)
{
cout << "Call PrintAdd, Value = " << a + b << endl;
}
DECLARE_FUNCTION_MULTICAST_DELEGATE(FAddDelegate, int, int)
int main()
{
FAddDelegate OnAdd;
A a;
B* b = new B();
OnAdd.AddFunc(&a, &A::PrintAdd);
OnAdd.AddFunc(b, &B::PrintAdd);
OnAdd.AddFunc(&PrintAdd);
OnAdd.BroadCast(5, 10);
delete b;
return 0;
} 通过DECLARE_FUNCTION_MULTICAST_DELEGATE(FAddDelegate, int, int)定义了多播委托FAddDelegate,声明一个多播委托OnAdd,绑定对象a函数,对象b的函数,和全局函数,执行多播,结果如下:

既然原生C++没有委托,那我们就试着造一个轮子吧。
C++委托源代码链接:https://github.com/YGXXD/cpp-delegate