在程序中使用批量事件通知是很常用的场景,该事件分发器可以绑定多个静态与非静态函数,内部使用标准库的list和function实现,可变模板参数可以拓展到任意长度。

模仿C#中的委托,分为Events、Delegate、Action和Function。以下是继承的结构。

  • Events (添加移除静态与实例事件)
    • Delegate (执行事件、按实例移除或全部移除)
      • Action (Delegate特化版本)
      • Function (继承Delegate,实现带返回值列表的执行事件)
      • Function<bool> (Function偏特化版本,实现返回值验证是否存在false)
  • ActionEvents (Event特化版本)
  • FunctionEvents (Event特化版本)

Events作为模板需要传入返回值与形参类型,同时Events作为基类,只能使用添加事件和移除事件,并不可以执行。而执行函数是在Delegate层以上的,因为ActionEvents和FunctionEvents只是Events的具体化,Action只是Delegate的具体化,所以就可以有以下的继承关系。

  • ActionEvents (Events)
    • Action (Delegate)
  • FunctionEvents (Events)
    • Delegate
      • Function
      • Function<bool>

Events类应该实现以下几个功能:

  1. 添加与移除函数指针的事件(普通函数、成员静态函数与无捕获的lambda表达式)
  2. 添加与移除实例事件(成员实例函数)
  3. 通过索引来移除闭包lambda
  4. 提供+=与-=运算符重载(仅函数指针类型)

Delegate类功能实现:

  1. Invoke执行所有事件
  2. 按实例对象移除事件
  3. 移除所有事件

普通的函数指针因为在内存中仅存在一份,所以在移除时可以方便的直接对保存的函数指针进行对比。

带捕获的lambda表达式可以直接转换成std::function类型,采用索引的方式来控制:将函数保存后,返回一个自增的索引,外部可以通过这个索引来移除。或者在创建时传入一个实例对象,最后按实例移除事件。

普通的实例函数就需要使用std::bind对实例进行绑定,但是绑定后就没办法对实例和成员指针进行判断,所以使用成员指针来比较,C++的成员指针必须要求指定的类型,所以使用类模板,并通过继承来做类型擦除,类型擦除后就可以将它们保存到一起,同时取值函数也是一个模板函数,在取值时对实例进行基类至子类的转换。

所有事件的绑定绑定都会返回一个索引值,用来标示该事件,主要用于没有名字函数的移除,在单独移除事件时需要该索引值,在不需要移除单事件或移除全部的情况下该索引可以丢弃。

使用:

{{EJS0}}

输出结果:

{{EJS1}}

该源码你可以在 https://github.com/JomiXedYu/JxCode.CoreLib/blob/main/CoreLib/Events.hpp 找到。

源码:

{{EJS2}}