C++11 实现一个通用的消息总线框架

1,793 阅读3分钟

「这是我参与11月更文挑战的第 7 天,活动详情查看:2021最后一次更文挑战」。

参加该活动的第 12 篇文章

参考原文地址: (原创) 一个通用的 C++ 消息总线框架

我对文章的格式和错别字进行了调整,并在他的基础上,根据我自己的理解把重点部分进一步解释完善(原作者的代码注释甚少)。以下是正文。

正文

背景

应用开发过程中经常会处理对象间通信的问题,一般都是通过对象或接口的依赖和引用去实现对象间的通信,这在一般情况下是没问题的,但是如果相互通信的对象很多,对象间的引用关系像蜘蛛网一样关系很复杂,则难以维护。

解决方案

解决这个问题的一个好方法是通过消息总线(MessageBus)去解耦对象间复杂的相互引用关系。

设计思路

被通信对象向消息总线发布一个主题,这个主题包含消息主题消息类型消息处理函数

  • 消息主题标示某个特定的主题,
  • 消息类型用来标示这个主题会响应某个特定对象的消息,
  • 消息处理函数用来响应该主题的某种消息类型。

通信对象向消息总线发送某个特定消息主题和某个特定消息类型的消息,总线就会根据消息主题消息类型找到对应的消息处理函数处理该请求。

实例代码

代码与详细注释

#pragma once
#include <boost/tuple/tuple.hpp>
#include <boost/utility.hpp>
#include <boost/unordered_map.hpp>
#include <boost/any.hpp>

/// @note 前向声明
template <typename... Args>
struct Impl;

/// @note 基本定义
template <typename First, typename... Args>
struct Impl<First, Args...>
{
    /// @note 消息类型名字:融合了参数名字的字符串
    static std::string name()
    {
        return std::string(typeid(First).name()) + " " + Impl<Args...>::name();
    }
};

/// @note 递归终止
template <>
struct Impl<>
{
    static std::string name()
    {
        return "";
    }
};

/// @note 返回该类型名字
template <typename... Args>
std::string type_name()
{
    return Impl<Args...>::name();
}

class MessageBus : boost::noncopyable
{
public:
    /// @note 根据主题和消息类型注册消息处理函数
    /// 首先将(可变参的)成员函数(即消息处理函数)用 function 对象进行封装
    /// 然后向 map 注册该 function 对象,其中主题用来生成 map 的 key
    template <typename... TArgs, typename TObject, typename TMember>
    void Attach(string strTopic, TObject *Object, TMember Member)
    {
        std::function<void(TArgs...)> f = std::function<void(TArgs...)>([=](TArgs... arg)
                                                                        { (Object->*Member)(arg...); });

        m_map.insert(make_pair(GetKey(strTopic), f));
    }

    /// @note 向总线发送主题和消息类型。消息总线收到该消息后会找到并调用对应的消息处理函数。
    template <typename... Args>
    void SendReq(string strTopic, Args... args)
    {
        auto range = m_map.equal_range(GetKey(strTopic));
        boost::unordered_multimap<string, boost::any>::iterator it;

        for (it = range.first; it != range.second; it++)
        {
            std::function<void(Args...)> f = boost::any_cast<std::function<void(Args...)>>(it->second);
            f(args...);
        }
    }

    /// @note 移除某个主题, 需要主题
    template <typename... Args>
    void Remove(string strTopic)
    {
        auto it = m_map.find(GetKey(strTopic));
        while (it != m_map.end())
            m_map.erase(it++);
    }

private:
    /// 根据主题获得消息键值,以便确定消息处理函数
    template <typename... TArgs>
    string GetKey(string &strTopic)
    {
        return strTopic + type_name<TArgs...>();
    }

private:
    boost::unordered_multimap<string, boost::any> m_map;
};

测试代码:

MessageBus bus;
MyStruct st;
bus.Attach<int, string>("bb", &st, &MyStruct::Test); ///< 注册(topic、消息类型、消息处理函数)
bus.Attach<int, string>("bb", &st, &MyStruct::Test2);
bus.SendReq<int, string>("bb", 0, " append");        ///< 发送消息处理请求(主题和消息类型)
bus.Remove<int, string>("bb");                       ///< 移除(主题和消息类型)

测试结果:

it is a test: 0 append

it is a test2: 0 append

更新版本

通过万能的函数包装器实现消息总线,使得接口的调用更加通用和一致。 新增支持非成员函数、const 成员函数

template <typename R = void>
class MessageBus : boost::noncopyable
{
public:
    // 注册
    
    /// @note 非成员函数
    template <class... Args, class F, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
    void Attach(string strKey, F &&f)
    {
        std::function<R(Args...)> fn = [&](Args... args)
        { return f(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name<Args...>(), std::move(fn)));
    }

    /// @note 非 const 成员函数 —— R (C::*f)(DArgs...)
    template <class... Args, class C, class... DArgs, class P>
    void Attach(string strKey, R (C::*f)(DArgs...), P &&p)
    {
        std::function<R(Args...)> fn = [&, f](Args... args)
        { return (*p.*f)(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name<Args...>(), std::move(fn)));
    }
    
    /// @note const 成员函数 —— R (C::*f)(DArgs...) const
    template <class... Args, class C, class... DArgs, class P>
    void Attach(string strKey, R (C::*f)(DArgs...) const, P &&p)
    {
        std::function<R(Args...)> fn = [&, f](Args... args)
        { return (*p.*f)(std::forward<Args>(args)...); };
        m_map.insert(std::make_pair(strKey + type_name<Args...>(), std::move(fn)));
    }

    // 广播消息,主题和参数可以确定一个消息, 所有的消息接收者都将收到并处理该消息
    template <typename... Args>
    void SendReq(string strTopic, Args... args)
    {
        auto range = m_map.equal_range(strTopic + type_name<Args...>());
        for (auto it = range.first; it != range.second; it++)
        {
            std::function<R(Args...)> f = boost::any_cast<std::function<R(Args...)>>(it->second);
            f(args...);
        }
    }

    // 移除消息
    template <typename... Args>
    void Remove(string strTopic)
    {
        string strMsgType = GetNameofMsgType<Args...>();
        auto range = m_map.equal_range(strTopic + strMsgType);
        m_map.erase(range.first, range.second);
    }

private:
    std::multimap<string, boost::any> m_map;
};

测试代码:


/// @note 测试类型和函数
struct A
{
    void Test(int x) { cout << x << endl; }
    void GTest()
    {
        cout << "it is a test" << endl;
    }
    void HTest(int x) const
    {
        cout << "it is a HTest" << endl;
    }
};

void GG(int x)
{
    cout << "it is a gg" << endl;
}

void GG1()
{
    cout << "it is a GG" << endl;
}

/// @note 对消息总线的测试
void TestMessageBus()
{
    A a;
    MessageBus<> bus;
    bus.Attach<int>("aa", &A::Test, &a);
    int x = 3;
    bus.SendReq("aa", 3);

    bus.Attach<int>("hh", &A::HTest, &a);
    bus.SendReq("hh", x);
    bus.Attach("bb", &A::GTest, &a);
    bus.SendReq("bb");

    bus.Attach<int>("gg", GG);
    bus.SendReq("gg", 3);

    bus.Attach("gg", GG1);
    bus.SendReq("gg");
}