当前位置: 首页 > news >正文

实现一个简单回调列表

前面我们实现了简单的std::function,本节我们在之前的基础上实现一个可以存储任意回调函数的类,类似观察者模式,调用所有注册到被观察者的回调函数;类似Qt中信号槽,信号触发,调用所有连接到此信号的槽函数(不考虑异步);


我们先用std::function的方式来实现一个,接下来再将std::function替换为我们自己的函数包装类

#include <list>
#include <functional>// 原型
template<typename Signature>
class Event;// 特化
template<typename ReturnType, typename... Args>
class Event<ReturnType(Args...)>
{
private:using return_type = ReturnType;using function_type = ReturnType(Args...);using stl_function_type = std::function<function_type>;using pointer = ReturnType(*)(Args...);public:void operator += (stl_function_type func){if (func != nullptr){m_funcLst.push_back(std::move(func));}}void operator() (Args ...args){for (int i = 0; i < m_funcLst.size(); ++i){if (m_funcLst[i] != nullptr){m_funcLst[i](args...);}}}private:std::vector<stl_function_type> m_funcLst;
};
    Event<void(const std::string &, const bool)> event;X x;event += std::bind(&X::func, &x, std::placeholders::_1, std::placeholders::_2);event += &func;event += [](const std::string & s, const bool a){ std::cout << "lambda:" << s << a << std::endl; };event("std", true);

以上这个是使用C++11实现的一个回调函数列表类,借助了std::function来包装任意可调用对象


如果不借助std::function,需要怎样自行实现呢?

前面我们实现过一个简单的std::function类,
内部的实现主要是一个非模板接口类 ICallable,派生出 可以包装不同可调用对象的模板类实现这个接口,然后在function包装类中申明 ICallable接口类指针


我们这里也是使用一样的形式,只不过没有function这个包装类后,ICallable这个类就即应该是模板,也应该是接口,同样使用派生的方式,让不同的模板子类来实现这个接口

template<typename Signature>
struct ICallable;template<typename R, typename... Args>
struct ICallable<R(Args...)>
{virtual R invoke(Args&&... args) = 0;virtual  ~ICallable() = default;
};

有了以上的签名形式,接下来就需要不同的子类来实现这个接口,现在我们分别写出,包装普通函数和包装类成员函数的子类

//-------------------------------------------------------------------------
template<typename R, typename... Args>
struct NormalCallable : public ICallable<R(Args...)>
{using pFunc = R(*)(Args...);NormalCallable(pFunc p) : m_p(p){}virtual R invoke(Args&&... args) override{if (m_p != nullptr){return m_p(std::forward<Args>(args)...);}}pFunc m_p = nullptr;
};//-------------------------------------------------------------------------
template<typename Class, typename R, typename... Args>
struct MemCallback : public ICallable<R(Args...)>
{using pMemFunc = R(Class::*)(Args...);MemCallback(Class* obj, pMemFunc p) : _obj(obj), _p(p){}virtual R invoke(Args&&... args){if (_obj != nullptr && _p != nullptr){return (_obj->*_p)(std::forward<Args>(args)...);}}Class* _obj = nullptr;pMemFunc _p = nullptr;
};

这两个子类,分别实现了对普通函数和类成员函数的包装,由于定义模板类对象必须要指明模板参数,接下来我们可以实现两个辅助函数,用来推导模板参数

//-------------------------------------------------------------------------
//辅助函数
template<typename R, typename... Args>
std::unique_ptr<ICallable<R(Args...)>> make_delegate(R(*p)(Args...))
{return std::make_unique<NormalCallable<R, Args...>>(p);
}template<typename Class, typename R, typename... Args>
std::unique_ptr<ICallable<R(Args...)>> make_delegate(Class* obj, R(Class::*p)(Args...))
{return std::make_unique<MemCallback<Class, R, Args...>>(obj, p);
}

到这里,已经实现了使用make_delegate函数来包装普通函数以及类成员函数的功能,接下来我们再写一个容器类,可以将所有函数存储起来,依次调用

//-------------------------------------------------------------------------
template<typename Signature>
struct Callbacks;template<typename R, typename... Args>
struct Callbacks<R(Args...)>
{using CallbackFunc = std::unique_ptr<ICallable<R(Args...)>>;
public:void operator +=(CallbackFunc callback){m_lst.emplace_back(std::forward<CallbackFunc>(callback));}void operator()(Args&&... args){for (auto& cb : m_lst){cb->invoke(std::forward<Args>(args)...);}}private:std::vector<CallbackFunc> m_lst;
};

这个类非常简单,只是一个可调用函数的容器,重载了()操作符,依次调用注册进来的可调用函数
接下来我们看一看怎样使用:

void func(const std::string & s, const bool a)
{std::cout << "----normal func:" << s << a << std::endl;
}struct X
{void func(const std::string & s, const bool a){std::cout << "mem func:" << s << a << std::endl;}
};int main(int argc, char *argv[])
{Callbacks<void(const std::string &, const bool)> cbs;X x;cbs += make_delegate(&x, &X::func);cbs += make_delegate(&func);cbs("test", false);return 0;
}

以上这个类,其实就和duilib中的CEventSource类非常相似,其实也是模仿duilib写的,只不过duilib中固定了参数类型为void*,返回值类型为bool,我们这里没有做任何限制;

这个类目前还无法包装lambda表达式函数对象,怎样做才能实现对这两种类型的包装呢,我们后续再讲~


http://www.mrgr.cn/news/100712.html

相关文章:

  • 51c自动驾驶~合集37
  • Docker安装的mysql限制ip访问
  • ACTF2025 - WEB Excellent-Site
  • 二叉树知识点
  • 软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(第4版课程精华版)
  • 复旦大学发布全球首款二维半导体芯片——无极
  • LangChain入门(四) 部署应用程序
  • 2. python协程/异步编程详解
  • 细说STM32单片机FreeRTOS互斥量及其编程实例
  • Harbor默认Redis与Notary组件弱口令漏洞分析与修复指南
  • NVIDIA高级辅助驾驶领域的创新实践与云计算教育启示
  • 双系统安装 ios放同一个u盘 ventory使用+windows安装,双系统互相访问中间盘 切换默认启动系统
  • 数据分析1
  • 【大模型】Coze AI 智能体工作流从配置到使用实战详解
  • 软考高项(信息系统项目管理师)第 4 版全章节核心考点解析(力扬老师课程精华版)
  • 【Linux应用】交叉编译环境配置、ARM虚拟机环境编译,以及最简单粗暴的环境移植(直接从目标板上复制)
  • 9.idea中创建springboot项目
  • python脚本下载ERA5数据详细规范和教程
  • day006
  • phpstudy修改Apache端口号