C# 里的 delegate
C# 里的 delegate 作为语法特性的一部分,使用起来非常方便。
首先按照函数签名,声明一个 delegate 类型:
delegate void DelegateType();
之后就可以用这个 delegate 类型来声明 delegate 变量了。
DelegateType delegateMetods;
再之后,就可以用这个 delegate 类型的变量,来 注册 / 注销监听函数了。比如我们有两个函数 Test1,Test2
void Test1() {}
void Test2() {}
只要把这两个函数在 delegate 变量里使用 += 注册之后, 再对 delegate 变量做一次调用,Test1 和 Test2 两个方法就都能被执行到了。
delegateMethods += Test1;
delegateMethods += Test2;
delegateMethods();
使用 delegate 的机制几乎实现了一整套现成的观察者模式,使用起来十分方便。 但是, 如果我们使用 C++ ,如何能实现这样一个 delegate 的机制呢?
C++ delegate 类的接口设计
在 C++ 里实现 delegate 之前,我们可以先不考虑内部的实现,先看一下我们暴露给外界的接口,以及我们希望自己实现的 delegate 要如何被外部程序调用。
using PoolChanged = Delegate<void(Pool* pool, EntityPtr entity)>;
PoolChanged OnEntityCreated;
在上面的代码里,先声明一个 delegate 的类型 ,名为 PoolChanged. 对应到 C# 里 delegate 类型的声明。 在声明这个 delegate 类型的同时,也指定了 delegate 所对应的函数签名。 比如我们这个例子里,函数的返回值是 void, 参数包括 一个 Pool* , 一个 EntityPtr .
OnEntityCreated += [](Pool* pool,EntityPtr entity) {
.....
};
再之后,我们可以像上面的代码一样, 使用 += , -= 这样 类似 C# 的 方式,来注册/注销 监听函数。
OnEntityCreated(pool,entity);
也可以像上面代码这样,像调用函数一样, 调用 OnEntityCreated 对象,使得它能够调用到已绑定的函数里。
总之,我们希望实现出来的接口, 尽可能的接近 C# .
实现的思路可以总结为:
1. 实现一个 Delegate 的 类 . 这个类需要支持变长模版。
2. 这个类内部维护一个 std::function 的集合
3. 这个类需要支持 注册/注销 监听函数的功能
4. 还需要支持 对这个类的实例进行“调用” ,调用时执行所有内部维护的 std::function 集合里面的函数
5. 为了在语法上接近 C# ,还需要实现 operator += ,-= ,()
下面我们先实现一个简单的版本, 这个版本里将会有上面大部分的功能,但暂时不支持模版, 只支持 返回值为 void, 没有参数的 函数类型的 delegate
C++ delegate 的简单实现
我们先要有个类,名字就叫 class Delegate
class Delegate{};
这个类的核心,是要维护一个 std::function 的集合. 所以需要有这样的成员 .
#pragma once
#include <functional>
#include <list>
#include <memory>
namespace ayy
{
class Delegate
{
private:
std::list<std::shared_ptr<std::function<void()>>> mFunctionList;
};
}
之后,我们需要这个类能支持注册/注销 函数.我们把注册函数起名为 Connect, 注销函数起名为 Remove . 先来看一下 Connect 的实现
class Delegate
{
public:
void Connect(const std::function<void()>& func) {
mFunctionList.push_back(std::make_shared<std::function<void()>>(func));
}
private:
std::list<std::shared_ptr<std::function<void()>>> mFunctionList;
};
Connect 做的事情,就是把外面传进来的 std::function 的实例加到 Delegate 的成员变量 mFunctionList 里面管理起来。 我们看到目前支持的简化版本的 void() 类型的 std::function 出现了很多次,写起来比较冗长 。 我们可以把这个类型签名的函数起一个别名 FuncType
class Delegate
{
using FuncType = std::function<void()>;
public:
void Connect(const FuncType& func) {
mFunctionList.push_back(std::make_shared<FuncType>(func));
}
private:
std::list<std::shared_ptr<FuncType>> mFunctionList;
};
在上面的代码,用 using 给函数类型起了别名之后, 代码简化了不少。
再来看 Remove 的实现
void Remove(const FuncType& func)
{
mFunctionList.remove_if([&](const std::shared_ptr<FuncType>& funcPtr){
return (*funcPtr).target_type().hash_code() == func.target_type().hash_code();
});
}
Remove() 的实现里,可以通过 比较 mFunctionList 里的 target_type().hash_code() ,来判断参数是否在 mFunctionList 里。如果在的话,把这个 function 从管理列表里移除。
之后可以再实现一个 Clear() ,Clear() 简单的把 管理列表清空即可。
void Clear()
{
mFunctionList.clear();
}
现在,有了注册函数 Connect, 注销函数 Remove, 全部注销 Clear, 最后只需要实现最核心的 Invoke 进行调用, 这个 Delegate 类几乎就可以用了。
void Invoke()
{
for(auto it : mFunctionList)
{
(*it)();
}
}
Invoke() 函数的实现很简单,挨个把 mFunctionList 里面的所有 std::function 调用一遍即可。
至此, Delegate 类的全部代码长成这样:
#pragma once
#include <functional>
#include <list>
#include <memory>
namespace ayy
{
class Delegate
{
using FuncType = std::function<void()>;
public:
void Connect(const FuncType& func)
{
mFunctionList.push_back(std::make_shared<FuncType>(func));
}
void Remove(const FuncType& func)
{
mFunctionList.remove_if([&](const std::shared_ptr<FuncType>& funcPtr){
return (*funcPtr).target_type().hash_code() == func.target_type().hash_code();
});
}
void Clear()
{
mFunctionList.clear();
}
void Invoke()
{
for(auto it : mFunctionList)
{
(*it)();
}
}
private:
std::list<std::shared_ptr<FuncType>> mFunctionList;
};
}
我们在 main 函数里测试一下 :
#include “Copy/Delegate.h”
using namespace ayy;
int main(int argc, const char * argv[]) {
using DelegateType = Delegate;
DelegateType delegateInstance;
auto f1 = [](){printf("f1\n");};
auto f2 = [](){printf("f2\n");};
auto f3 = [](){printf("f3\n");};
delegateInstance.Connect(f1);
delegateInstance.Connect(f2);
delegateInstance.Connect(f3);
delegateInstance.Invoke();
delegateInstance.Remove(f2);
delegateInstance.Invoke();
return 0;
}
输出结果:
f1
f2
f3
f1
f3
Program ended with exit code: 0
由此可见,Connect, Remove, Invoke 都能起到作用。
重载 operator ,让 Delegate 类用起来更像 C#
C# 里面的 delegate 是可以通过 += ,-= 来注册, 注销函数,能够通过 () 来实现 delegate 的调用的。 在 C++ 里, 只需要 重载 +=, -=, () 这3个运算符,即可让 C++ Delegate 类的实类也能有类似的效果。
我们在 class Delegate 里面重载这几个 operator
void operator += (const FuncType& func)
{
return Connect(func);
}
void operator -= (const FuncType& func)
{
return Remove(func);
}
void operator ()()
{
return Invoke();
}
在 main 函数里面重新做一次测试 :
#include "Copy/Delegate.h"
using namespace ayy;
int main(int argc, const char * argv[]) {
using DelegateType = Delegate;
DelegateType delegateInstance;
auto f1 = [](){printf("f1\n");};
auto f2 = [](){printf("f2\n");};
auto f3 = [](){printf("f3\n");};
delegateInstance += f1;
delegateInstance += f2;
delegateInstance += f3;
delegateInstance();
delegateInstance -= f2;
delegateInstance();
return 0;
}
输出的结果是相同的。这样一个仅支持 void 返回值, 无参数类型的 Delegate 实现完成了。 下面我们借助 C++ template 的特性, 来支持各种函数签名的 Delegate .
变长模版实现支持各种函数的版本
目前,我们的 Delegate 全部代码长成这样:
#pragma once
#include <functional>
#include <list>
#include <memory>
namespace ayy
{
class Delegate
{
using FuncType = std::function<void()>;
public:
void Connect(const FuncType& func)
{
mFunctionList.push_back(std::make_shared<FuncType>(func));
}
void Remove(const FuncType& func)
{
mFunctionList.remove_if([&](const std::shared_ptr<FuncType>& funcPtr){
return (*funcPtr).target_type().hash_code() == func.target_type().hash_code();
});
}
void Clear()
{
mFunctionList.clear();
}
void Invoke()
{
for(auto it : mFunctionList)
{
(*it)();
}
}
void operator += (const FuncType& func)
{
return Connect(func);
}
void operator -= (const FuncType& func)
{
return Remove(func);
}
void operator ()()
{
return Invoke();
}
private:
std::list<std::shared_ptr<FuncType>> mFunctionList;
};
}
为了支持各种类型的函数签名, 我们开始着手把它改造为模版类。
对于 Delegate 类来说, 需要让它注册的函数支持各类的参数。我们使用 C++ 11 变长模版的特性。 在 Delegate 类上做如下声明 :
template<typename... TArgs>
class Delegate
{
...
};
Delegate 类里维护的 std::function ,也要由 std::function<void()> 改为 std::function<void(TArgs… args)> 。由于我们之前提出了 using FuncType 的语句,因此这里只改动这一行即可
using FuncType = std::function<void(TArgs... args)>;
在实际的函数调用的地方,我们也要把 不定长的参数传递过去 ,为此,需要修改 Invoke 和 operator() 两个地方
void Invoke(TArgs... args)
{
for(auto it : mFunctionList)
{
(*it)(args...);
}
}
void operator ()(TArgs... args)
{
return Invoke(args...);
}
这里需要注意 三个点 “…” 作为参数时的语法 。
至此,我们的 Delegate 类基本改造完毕。在 main 函数里面 我们重新写一下 测试代码
#include "Copy/Delegate.h"
using namespace ayy;
int main(int argc, const char * argv[]) {
using DelegateType = Delegate<int,const char*>;
DelegateType delegateInstance;
auto f1 = [](int i,const char* s){printf("f1,%d,%s\n",i,s);};
auto f2 = [](int i,const char* s){printf("f2,%d,%s\n",i,s);};
auto f3 = [](int i,const char* s){printf("f3,%d,%s\n",i,s);};
delegateInstance += f1;
delegateInstance += f2;
delegateInstance += f3;
delegateInstance(1,"ayy1");
delegateInstance -= f2;
delegateInstance(2,"ayy2");
return 0;
}
上面代码的改动在于, 可以给 Delegate 声明时,增加参数类型。 并且在注册 f1,f2,f3 时,要求 这些 std::function 必须和 声明 Delegate 时的函数类型保持一致。
调用 delegate 的时候,也需要传递和 delegate 声明时匹配的参数即可。
输出结果
f1,1,ayy1
f2,1,ayy1
f3,1,ayy1
f1,2,ayy2
f3,2,ayy2
Program ended with exit code: 0
至此, C++ 支持 C# 的 Delegate 类的实现基本完成。
总结
C++ 实现 C# 的 delegate 主要依赖于 C++ 11 的 std::function 和 变长模版 的特性。活用这两个特性之后,实现这样一个 Delegate 类并不困难。有了这个类,就可以在 C++ 里愉快的 使用 delegate 的语法了。