前面我们实现了简单的std::function
,本节我们在之前的基础上实现一个可以存储任意回调函数的类,类似观察者模式,调用所有注册到被观察者的回调函数;类似Qt中信号槽,信号触发,调用所有连接到此信号的槽函数(不考虑异步);
我们先用std::function
的方式来实现一个,接下来再将std::function
替换为我们自己的函数包装类
#include
#include
// 原型
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表达式
和函数对象
,怎样做才能实现对这两种类型的包装呢,我们后续再讲~