模板写的不多,需要的时候都是网上找找然后改改。对一些概念体会也不深。
正好解决了一个也不能说是问题,算是一个需求吧:让观察者模板在给观察者发消息时,参数让编译器来推导。(之前是写死了)
这个模板,可以接受函数、lambda以及静态类函数作为观察者。
template <typename Func>
class Events {
public:
/// 注册观察者,支持右值引用
int Connect(Func&& f, const std::wstring& prompt=L"", int groupType = EventsPriorityEnum::Group1) {
return Assign(std::forward<Func>(f), groupType, prompt); //这里虽然用了forward,但由于此Connect的参数并不是进行直接推导的,所以f已经是左值了
}
/// 注册观察者,左值
int Connect(const Func& f, const std::wstring& prompt = L"", int groupType = EventsPriorityEnum::Group1) {
return Assign(f, groupType, prompt);
}
/// 移除观察者
void Disconnect(int key, const std::wstring& prompt = L"", int groupType = EventsPriorityEnum::Group1) {
m_connections.erase(std::make_tuple(key, groupType,prompt));
}
void Clear()
{
m_connections.clear();
}
//template <typename ... Args>
//void Notify(Args&& ... args) {
// for (auto &it : m_connections) {
// auto& key = it.first;
// auto& func = it.second;
// if(func(std::forward<Args>(args)...))
// {
// std::string info = fmt::format("{}执行出错", StringUtil::ws2s(std::get<2>(key)));
// PromptInfoMgr::GetInstance().Info(info);
// }
// }
//}
void Notify(boost::any data) {
for (auto &it : m_connections) {
auto& key = it.first;
auto& func = it.second;
if (!func(data))
{
std::string info = fmt::format("{}执行出错", StringUtil::ws2s(std::get<2>(key)));
PromptInfoMgr::GetInstance().Info(info);
//PromptInfoMgr::GetInstance().Info(info);
}
}
}
private:
/// 保存观察者并分配观察者编号,目前没什么用。没有考虑非常精细得来控制观察者的执行,只按优先级规定的顺序来执行即可
//template <typename F>
int Assign(Func&& f, int groupType = EventsPriorityEnum::Group1, const std::wstring& prompt = L"") {
int k = m_observerId++;
m_connections.emplace(std::make_tuple(groupType,k,prompt), std::forward<Func>(f));
return k;
}
int m_observerId = 0; //观察者对应编号
std::map<std::tuple<int/*gorup*/,int/*ObserverId*/,std::wstring>, Func> m_connections; // 观察者列表
};
调用,注册一个lambda:
Events<std::function<void(boost::any)>> events;
events.Connect([](boost::any){
std::cout<<"我被调用了"<<std::endl;
});
events.Notify(0);
但后来发现有个问题,如果我用Events<std::function<void(int,int)>>进行实例化,在调用Notify的时候,编译就有问题了。因为void(int,int)是匹配不上boost::any的。
所以,对于Notify,还是要使用变参数列表:
template <typename ... Args>
void Notify(Args&& ... args) {
for (auto &it : m_connections) {
auto& key = it.first;
auto& func = it.second;
if(func(std::forward<Args>(args)...))
{
std::string info = fmt::format("{}执行出错", StringUtil::ws2s(std::get<2>(key)));
PromptInfoMgr::GetInstance().Info(info);
}
}
}
调用:
Events<std::function<bool(int,int)>> events;
events.Connect([](int x,int y){
std::cout<<"我被调用了"<<std::endl;
return true;
});
events.Notify(1,2);
变参数版本。注意Notify函数的实例化时机:
在用具体类型(比如std::function)对Events的typename Func在定义时,其实已经把函数的参数和返回值都定义好了。比如想记录函数bool fun(int x, int y),Func可以定义为std::function<bool(int, int)>。
Func要调用的参数,虽然在std::function里定义,但个数未知。不能用boost::any来承接,这样的话就是假设只有一个参数了。
所以这里要用变参数进行调用,让编译器进行推导。这样的话就把实例化的任务交到了实例化Notify的的各个调用处(比如dll)。
当编译器看到Events<std::function<bool(int, int)>>时,它只把除了Notify之外的函数给实例化了。而且其实已经把调用的规则给定了下来,
所以在调用Notify的时候也得按照这个规则来调用,否则就会推导失败。在调用Notify的地方,又对Notify进行了实例化。
只是Notify此时还没有实体(在HchxKernel编译的时候)。
这样的化,Events就会有多个Notify的版本,散落在各个模块(实例化它的地方)