0%

cpp模仿C#事件机制(1) 无参数版实现

cpp模仿C#事件机制(1) 无参数版实现

起因

  • 之前写unity时,感觉c#的事件机制超好用,就想在万能的c++中实现一下
  • 看了网上的一些实现,大部分都是只能注册一个回调函数的,这有啥用???
  • 要么就是使用时要继承一大堆东西,一点也不方便

设计

  • 本身很简单,就是一个观察者模式,麻烦的是对回调函数的管理
    • 需要一个容器存储回调函数,set,vector都行
    • 要装进容器中,那么你的回调函数就要支持一些符号
  • 这里实现了两种事件发布器
    • 基于std::unordered_set,重复插入会返回false,调用顺序和插入顺序无关
    • 基于std::vector,允许重复插入,调用顺序和插入顺序相同
  • 两种事件发布器都提供注册常驻回调函数和一次性回调函数接口(扩展了下,还是有这方面的需求的)
  • 多线程只是简单的分类锁了下,有需要的话可以扩展各种无锁生产者消费者模型或者由订阅器维护回调函数,发布器维护订阅器引用
  • 回调函数过期
    • 回调函数已经被释放会导致空指针问题,程序会core,c#中也有这个问题
    • 这里尽可能解决下
      • 使用std::shared_ptr包裹回调函数,订阅器自己必须维护ptr
      • 发布器使用std::weak_ptr来指向回调函数
      • 已经过期的直接从队列中删除,报告下
    • 这样就解决了大部分过期问题
    • 现存的问题
      • 当回调函数线程已经通过检测了,准备调用,这时订阅器被销毁了,还会有问题
      • 如果再引入锁的话开销有些大,先这样吧
  • 最终设计
    • 订阅器使用std::weak_ptr<std::function<void()>>作为回调函数
    • 发布器使用std::shared_ptr<std::function<void()>>来保存回调函数引用
    • 有参数的,类中的方法,仿函数之类的使用std::bind包装为std::function<void()>

用法

  • 不需要任何继承

  • 任何我能想到的c++调用方式都是支持的

实现

  • 本身挺简单的,就是一个观察者模式
  • 额外实现了
    • 多线程安全:使用锁
    • 一次性任务:执行完成后删除自身
    • 空指针任务抛弃:weak_ptr lock失败的直接删掉
  • 只需要给std::weak_ptr实现hash和equal函数就行
    • using DelegrateWeakSave = std::weak_ptr<std::function<void()>>;
      using DelegrateDefine = std::shared_ptr<std::function<void()>>;
      
      struct HandleBindHash {
          std::size_t operator()(DelegrateWeakSave const& ptr) const {
              if (auto spt = ptr.lock()) {  // 使用之前必须复制到 shared_ptr
                  return reinterpret_cast<size_t>(&(*spt));
              } else {
                  return 0;
              }
          }
          std::size_t operator()(DelegrateDefine const& ptr) const { return reinterpret_cast<size_t>(&(*ptr)); }
      };
      struct HandleBindEqual {
          bool operator()(const DelegrateWeakSave& lhs, const DelegrateWeakSave& rhs) const {
              return HandleBindHash()(lhs) == HandleBindHash()(rhs);
          }
      };
      
    • 这里简单使用地址作为hash地址,由于是用shared_ptr来包裹std::function的,所以没问题
  • 剩下的就是一个观察者模式了,没啥说的

完整代码