当前位置: 首页 > 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.xdnf.cn/news/222031.html

相关文章:

  • 代理对象的mock
  • 数据库所有知识
  • 随机游走之 个人的简单理解
  • 通义千问Qwen3全维度解析
  • Windows系统下,导入运行 RuoYi-Vue3 项目
  • 批量打印工具使用全攻略
  • 深度循环神经网络
  • 链表反转操作经典问题详解
  • python之数字类型的操作
  • 【linux网络】网络基础概念
  • 从零构建Dagster分区管道:时间+类别分区实战案例
  • 企业的AI转型:生死时速的进化之路
  • 再学GPIO(三)
  • 系统设计中三高指什么
  • OpenGL学习笔记(PBR)
  • LayerSkip: Enabling Early Exit Inference and Self-Speculative Decoding
  • 大模型与MCP:重塑AI应用的新篇章
  • 手动安装OpenSSL1.1.1
  • 【深度解析】YOLOE登场:CNN路线的开放世界新答卷,超越YOLO-World与Transformer
  • 去哪儿旅行 Bella Pre 分析
  • (003)Excel 在滚动的时候,保持标题栏可见
  • 论文阅读的三个步骤
  • nextcloud私有网盘系统搭建
  • 【AI提示词】第一性原理
  • Laravel基础
  • 基于PLC的图书管理识别系统设计
  • 修复典籍知识问答典籍管理界面典籍不能正确加载的问题
  • IAP远程升级入门讲解
  • 第十五章-PHP文件编程
  • Docker与Vmware网络模式的对别