实现一个简单回调列表
前面我们实现了简单的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表达式
和函数对象
,怎样做才能实现对这两种类型的包装呢,我们后续再讲~