C++ 实现 C# delegate 机制

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 的语法了。

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值