观察者(模板)的一点体会

模板写的不多,需要的时候都是网上找找然后改改。对一些概念体会也不深。

正好解决了一个也不能说是问题,算是一个需求吧:让观察者模板在给观察者发消息时,参数让编译器来推导。(之前是写死了)

这个模板,可以接受函数、lambda以及静态类函数作为观察者。

template 
class Events {
public:
    /// 注册观察者,支持右值引用
    int Connect(Func&& f, const std::wstring& prompt=L"", int groupType = EventsPriorityEnum::Group1) {
        return Assign(std::forward(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 
    //void Notify(Args&& ... args) {
    //    for (auto &it : m_connections) {
    //        auto& key = it.first;
    //        auto& func = it.second;
    //        if(func(std::forward(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 
    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(f));
        return k;
    }

    int m_observerId = 0; //观察者对应编号
    std::map, Func> m_connections; // 观察者列表
};


调用,注册一个lambda:
Events> events;
events.Connect([](boost::any){
std::cout<<"我被调用了"<

但后来发现有个问题,如果我用Events>进行实例化,在调用Notify的时候,编译就有问题了。因为void(int,int)是匹配不上boost::any的。

所以,对于Notify,还是要使用变参数列表:

template 
void Notify(Args&& ... args) {
    for (auto &it : m_connections) {
        auto& key = it.first;
        auto& func = it.second;
        if(func(std::forward(args)...))
        {
            std::string info = fmt::format("{}执行出错", StringUtil::ws2s(std::get<2>(key)));
            PromptInfoMgr::GetInstance().Info(info);
        }
    }
}


调用:
Events> events;
events.Connect([](int x,int y){
std::cout<<"我被调用了"<

变参数版本。注意Notify函数的实例化时机:
在用具体类型(比如std::function)对Events的typename Func在定义时,其实已经把函数的参数和返回值都定义好了。比如想记录函数bool fun(int x, int y),Func可以定义为std::function


Func要调用的参数,虽然在std::function里定义,但个数未知。不能用boost::any来承接,这样的话就是假设只有一个参数了。
所以这里要用变参数进行调用,让编译器进行推导。这样的话就把实例化的任务交到了实例化Notify的的各个调用处(比如dll)。


当编译器看到Events>时,它只把除了Notify之外的函数给实例化了。而且其实已经把调用的规则给定了下来,
所以在调用Notify的时候也得按照这个规则来调用,否则就会推导失败。在调用Notify的地方,又对Notify进行了实例化。
只是Notify此时还没有实体(在HchxKernel编译的时候)。
这样的化,Events就会有多个Notify的版本,散落在各个模块(实例化它的地方)

你可能感兴趣的:(c++,算法,开发语言)