C++ 实现 C# delegate 机制

2023-11-03

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 实现 C# delegate 机制 的相关文章

随机推荐

  • Windows:‘git‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件。

    当使用 git在Windows环境下 命令克隆项目时 出现了问题 git 不是内部或外部命令 也不是可运行的程序或批处理文件 解决方法 安装 git 下载 双击下载的 git 安装包 默认设置 一路 next 最终安装成功 尝试使用 git
  • iOS 佛祖保佑,永无bug等有意思的注释

    复制 ooOoo o8888888o 88 88 O O
  • using C# to post XML to an https service (non-SOAP) RSS

    using System using System Collections using System Configuration using System Data using System Linq using System Web us
  • 异步赠书1月书讯:Python/深度学习/贝叶斯/OpenStack/DevOps/Docker

    Ladys and乡亲们 异步图书君祝大家元旦快乐 忙着写2017年总结的真爱粉儿们 2018年 你定了哪些新计划 留言说给大家听听 异步图书君送出好书一本 2018年异步图书君有一个超逼格计划 每天推荐一本好书 每月开设一门视频微课 希望
  • ​TypeScript基本知识点整理——变量类型

    在Javascript中 前面一篇文章介绍了TypeScript 微信公众号中的一篇文章 相信看过这边文章的人应该对Ts有所了解了 这篇文章大家和我一起来了解下TS的基本知识点吧 在开始之前我们先装环境 npm i typescript g
  • Qt窗体之间相互传递值的三种方式

    才学习QT编程时 按公司需求编写上位机时 遇到传值的问题 所总结的三种方式 希望能帮到一些初学者 方式1 使用QT中的signal slot机制进行传值 槽函数机制 一个窗口用来接收传递过来的值connect 另一个窗口发送信号signal
  • Some problems were encountered while building the effective model for...Maven出现该警告解决方法

    Some problems were encountered while building the effective model for cn itcast travel war 1 0 SNAPSHOT build plugins pl
  • 技术至简-3:简述无线通信系统中的数字调制

    在移动通信中 所谓数字调制 就是用一个电磁波的波形表示二进制 代表波形的参数有 电磁波的频率 电磁波的幅度 电磁波的相位 如果一个电磁波的波形代表1个比特 1或者0 则成为二进制调制 如果一个电磁波的波形代表N个比特 N gt 2 如000
  • 负载均衡部署方式《CDN技术详解》

    1 负载均衡部署方式 负载均衡设备在具体实现中分直连和旁挂两种部署方式 此外 为了提高网络可用性 负载均衡设备的双机热备部署也是十分必要的 常用负载均衡部署方式 1 直连部署方式 直连部署方式比较简单 就是将负载均衡设备直接部署在报文必经之
  • 【数据结构】栈---C语言版(详解!!!)

    文章目录 一 栈的概念及结构 1 栈的概念定义 2 动图演示 入栈 出栈 整体过程 二 栈的实现 三 数组结构栈详解 创建栈的结构 接口1 定义结构体 ST 接口2 初始化 STInit 接口3 销毁 STDestroy 接口4 入栈 ST
  • QT 数据导出到Excel

    转载自QT 数据保存到Excel 并把异常数据标红 qt保存excel文件 小华昭的博客 CSDN博客 在Qt自带的axcontainer模块中 我们可以使用QAxObject类来将数据保存到Excel中 Qt中将数据保存到Excel通常有
  • Python3 threading模块创建线程(一)

    文章目录 前言 一 使用 threading 模块创建线程 二 资源独占 前言 threading 模块除了包含 thread 模块中的所有方法外 还提供的其他方法 threading currentThread 返回当前的线程变量 thr
  • 进程的同步与异步

    同步与互斥 互斥是更严格的一种同步 进程同步 这是进程间的一种运行关系 同 是协同 按照一定的顺序协同进行 有序进行 而不是同时 即一组进程为了协调其推进速度 在某些地方需要相互等待或者唤醒 这种进程间的相互制约就被称作是进程同步 这种合作
  • leetcode刷题:z字形变换

    题目 图解思路 实现代码如下 class Solution public string convert string s int numRows if numRows lt 2 return s vector
  • java复制文件及文件夹

    java复制文件及文件夹 文件复制 复制源文件到目标文件 param sourcePath 源文件路劲 param targetPath 目标文件路径 public static void copyByStream String sourc
  • [毕业设计]2023-2024年最新电子信息工程专业毕设选题题目推荐汇总

    文章目录 1前言 2 如何选题 3 选题方向 3 1 嵌入式开发方向 3 2 物联网方向 3 3 移动通信方向 3 4 人工智能方向 3 5 算法研究方向 3 6 移动应用开发方向 3 7 网络通信方向 3 8 学长作品展示 4 最后 1前
  • matlab主成分分析散点图_主成分分析(PCA)的详细解释

    原作者 Zakaria Jaadi 翻译 钟胜杰 这篇文章的目的是提供主成分分析的完整同时比较简化的解释 特别是逐步回答它是如何工作的 这样每个人都可以理解它并利用它 而不必具有很高的数学水平 PCA实际上是一种使用很广的网络方法 但只有少
  • 【100%通过率 】【华为OD机试python】机房布局【2023 Q1

    2023华为OD机试 刷题指南 点这里 华为OD机试 题目列表 2023Q1 点这里 题目描述 小明正在规划一个大型数据中心机房 为了使得机柜上的机器都能正常满负荷工作 需要确保在每 个机柜边上至少要有一个电箱 为了简化题目 假设这个机房是
  • python之pefile模块(解析PE)

    发现很多的朋友经常用到PE格式相关的开发 如解析PE文件的格式 获取相关的内容 比如常常用到的静态的病毒启发式检测模型的建立 病毒样本分类 查壳脱壳等 搜索了一下发现论坛里面没有我要讲的这个东西 于是我在这里向大家推荐pefile这个pyt
  • C++ 实现 C# delegate 机制

    C 里的 delegate C 里的 delegate 作为语法特性的一部分 使用起来非常方便 首先按照函数签名 声明一个 delegate 类型 delegate void DelegateType 之后就可以用这个 delegate 类