c++:muduo使用template保存std::function的参数类型

概览

最近看到陈硕老师的muduo的7.6实现protobuf编解码器与消息分发器,觉得消息分发器这里写的确实很妙,简述一下背景,做业务的时候我们常会在tcp上制定一个消息格式,通过这些消息进行通讯,消息除了长度,类型,消息体为了最大压缩会使用pb,然后陈老师制作一个如下的消息格式样例

+-+-+-+-+-+-+-+-+-+-+-
+-      len         +- 4 bytes : 包的总长度
+- pb type name len +- :pb 类型名字长度
+-    pb type     \0+- : pb 类型名字,提供反序列化功能
+-    pb data       +- : pb的序列化数据
+-    check sum     +- 4 bytes: 校验码
+-+-+-+-+-+-+-+-+-+-+-

(虽然我个人觉得第二项pb类型名字没用,因为pb type以为\0结束已经很好的起到了分割符的作用了),当我们收到这个包的时候,进行解包通过pb type和pb data就可以得到一个完整的pb,但是接收pb的回调函数是muduo中处理的很精妙的一点,

因为函数指针以及std::function是不支持多态的,对于一个底层的消息分发器而言要保存所有消息的回调,是无法使用一个函数指针数组去保存的,muduo使用了一个类CallBack将回调函数放在了里面,因为所有的pb消息都继承于message,在这个function使用dynamic_cast将message做了一次转化转成了具体的子类消息,然后才将消息传递到具体的callback中,如下图, 这里很巧妙的一点是使用模板,将子类消息类型保存下来以便于dynamic_cast做转换,同时所有的CallBack都继承一个基类CallBack,那么只需要保存CallBackT指针在分发器中,利用虚函数多态进行调用即可。

2.正文

如概览所描述,muduo使用了一个类CallBack将回调函数放在了里面,使用模板,将子类消息类型保存下来以便于dynamic_cast做转换,同时所有的CallBack都继承一个基类CallBack,那么只需要保存CallBackT指针在分发器中,利用虚函数多态进行调用;来看看一个简单的例子实现

#include <iostream>
#include <algorithm>
#include <optional>
#include <vector>
#include <map>
#include <string>
#include <sstream>
#include <chrono>
#include <thread>
#include <functional>
#include <thread>
#include <condition_variable>
#include <atomic>
#include <memory>
#include <mutex>
#include <any>
using namespace std;

std::mutex mtx;
std::condition_variable cv;

class MsgBase
{
public:
    virtual ~MsgBase() {}
};

class MsgDerive1 : public MsgBase
{
public:
    ~MsgDerive1() override {}

    static std::string GetMsgType()
    {
        return "msg_derive1";
    }
};

class CallBack
{
public:
    virtual void OnCallBack(const MsgBase &msg) = 0;
};

template <typename T>
class CallBackT : public CallBack
{
public:
    typedef std::function<void(const T &msg_derive)> CallBackFun;
    CallBackFun cb_;

    CallBackT(const CallBackFun &cb) : cb_(cb) {}

    // 1
    void OnCallBack(const MsgBase &msg) override
    {
        const T &msg_specify = dynamic_cast<const T &>(msg); // 转换
        cb_(msg_specify); // 调用真正的回调函数
    }
};

void OutPutDeriveMsg(const MsgDerive1 &msg_derive)
{
    std::cout << msg_derive.GetMsgType() << std::endl;
}

int main()
{

    std::map<std::string, std::shared_ptr<CallBack>> call_back_map;

    // 注册监听MsgDerive1 消息的函数
    std::shared_ptr<CallBackT<MsgDerive1>> call_back_t(new CallBackT<MsgDerive1>(std::bind(OutPutDeriveMsg, std::placeholders::_1)));
    call_back_map.insert(std::pair(MsgDerive1::GetMsgType(), call_back_t));

    //a MsgDerive1 come
    MsgDerive1 msg_derive1;
    std::shared_ptr<CallBack> msg_handle_call_back = call_back_map.find(msg_derive1.GetMsgType())->second;
    msg_handle_call_back->OnCallBack(msg_derive1);
    return 0;
}

main()中创建了一个call_back_map,就把它当作消息分发器来保存回调对象,可以看到value是一个std::shared_ptr<CallBack>, 然后接下来创建一个MsgDerive1的CallBack对象并将其放入这个map中,接下来一个MsgDerive1消息过来了,通过在这个map中找到这个消息类型的callback对象,然后调用虚函数OnCallBack(),也就是CallBackT::OnCallBack(), 在这里面我们看到对消息进行dynamic_cast处理完成后,调用我们一开始放入的std::function,很妙~

3/ref

  • 3.1 <linux多线程服务器编程>
posted @ 2021-01-04 21:26  woder  阅读(552)  评论(0编辑  收藏  举报