C++委托
有个小小杜
编辑于 2024年08月08日 22:15

委托在开发过程中的重要性

        在游戏开发过程中,委托是一个必不可少东西,它可以动态绑定和解绑函数,可以使我们在开发过程中不必要过多的关注我们需要调用的对象,只需要关注调用的时刻,在SDL或者Win32开发中,其中窗体应用程序基本会使用循环来处理消息通知,而在消息通知中我们在获取到通知之后,该做什么事这个部分,就可以充分的利用委托来帮我们做。

例如:

代码块
C++
自动换行
复制代码
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的逻辑,这使得我们每次在添加一个物体的鼠标逻辑时,都需要修改一次消息处理的代码。

代码块
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++模板的知识了,我们定义一个委托类模板,通过不同模板参数来实例化不同的模板类,按照这个思路,我们开始来实现吧。


  • 先定义一个委托实现的接口:

代码块
C++
自动换行
复制代码
template<typename ReturnT, typename ...ArgsT>
struct IDelegate
{
	virtual ReturnT operator() (ArgsT... args) = 0;
};
复制成功

        定义一个接口IDelegate模板,绑定的函数有返回值和有传入参数,所以定义模板参数ReturnT为返回值类型,...ArgsT为传入参数类型。将operator()定义为纯虚函数,继承这个接口的类必须实现operator()函数,实现Delegate的多态,因为委托可能会绑定类成员函数,也可能会绑定非类成员函数。


  • 继承接口定义实现委托的类:

代码块
C++
自动换行
复制代码
//类成员函数模板
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()函数,来调用类的成员函数。

代码块
C++
自动换行
复制代码
//非成员函数模板
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()函数,来调用类的成员函数。


  • 实现单播委托:

代码块
C++
自动换行
复制代码
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()一样。(函数具体实现在文末尾的链接里)


  • 实现多播委托:

代码块
C++
自动换行
复制代码
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()一样。(函数具体实现在文末尾的链接里)


  • 添加宏定义:

代码块
C++
自动换行
复制代码
#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(委托名,参数...)来定义多播委托。


测试结果

代码块
C++
自动换行
复制代码
#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