描述
主要用于单线程的类之间的交互调用,实现了类似QT信号槽的调用,同时集成了观察者模式。创作不易,转载使用请标注来源,感谢。https://blog.csdn.net/weixin_42210846?type=blog
特性
- 仅需包含一个头文件
Communicator.h
,需要通信的类继承Communicator
即可。 - 消息通过枚举类型定义,支持任意枚举消息,添加枚举无需改动代码。枚举名称区分消息类型,枚举值区分具体消息
- 使用便捷,只需注册消息和消息处理函数
- 提供发送消息时,附带any传递任意额外信息
- 哈希方式存储消息和处理函数的映射,性能高,不存在多态等机制的开销
- 提供观察者模式供消息回传
对比QT信号槽
1.不支持多线程
2.使用同QT一样便捷,只需注册消息和消息回调函数。且如果使用QT的信号槽进行通信,不同的消息的类型则需要定义多个connect连接函数,如ELoginMsg、ERunMsg,多一种消息就要多一个connect
3.性能优于QT信号槽,通讯者收到消息后直接调用消息处理函数,QT信号槽的性能开销是普通函数调用的10倍
4.纯C++实现,不需要任何插件支持,如QT需要moc机制支持
5.使用到较多C++特性,需要学习成本
源码
Communicator.h
#pragma once
#include <any>
#include <functional>
#include <memory>
#include <unordered_map>
class Communicator : public std::enable_shared_from_this<Communicator> {
using MsgID = size_t;
using MsgCallback = std::any;
using CallbackFmt1 = std::function<void()>;
using CallbackFmt2 = std::function<void(const std::any&)>;
using CommunicatorPtr = std::shared_ptr<Communicator>;
using CommunicatorPtrVec = std::vector<CommunicatorPtr>;
public:
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void RegisterMsgHandle(T msg, CallbackFmt1 func) {
m_msgHandler.insert({ Hash(msg), func });
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void RegisterMsgHandle(T msg, CallbackFmt2 func) {
m_msgHandler.insert({ Hash(msg), func });
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void Handle(T msg, const std::any& info = std::any{}) {
size_t hash = Hash(msg);
if (bool findHandler = m_msgHandler.find(hash) != m_msgHandler.end()) {
m_msgHandler[hash].type() == typeid(CallbackFmt1) ? std::any_cast<CallbackFmt1>(m_msgHandler[hash])()
: std::any_cast<CallbackFmt2>(m_msgHandler[hash])(info);
}
}
// sigslot
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void Connect(T msg, CommunicatorPtr receiver) {
if (receiver != nullptr) {
m_receivers[Hash(msg)].push_back(receiver);
}
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void Emit(T msg, const std::any& info = std::any{}) {
if (bool findReceiver = m_receivers.find(Hash(msg)) != m_receivers.end()) {
for (const auto& receiver : m_receivers[Hash(msg)]) {
if (receiver != nullptr) {
receiver->Handle(msg, info);
}
}
}
}
// observer
void Add(CommunicatorPtr observer) {
if (bool notAdd = std::ranges::find(m_observers, observer) == m_observers.end()) {
m_observers.push_back(observer);
}
}
void Remove(CommunicatorPtr observer) {
if (auto findit = std::ranges::find(m_observers, observer); findit != m_observers.end()) {
m_observers.erase(findit);
}
}
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
void Notify(T msg, const std::any& info = std::any{}) {
for (const auto& observer : m_observers) {
if (observer != nullptr) {
observer->Handle(msg, info);
}
}
}
private:
template<typename T, typename = std::enable_if_t<std::is_enum_v<T>, void>>
MsgID Hash(T msg) const {
return std::hash<size_t>()(static_cast<size_t>(msg)) ^ std::hash <std::string>()(typeid(T).name());
}
private:
std::unordered_map<MsgID, MsgCallback> m_msgHandler;
CommunicatorPtrVec m_observers;
std::unordered_map<MsgID, CommunicatorPtrVec> m_receivers;
};
main.cpp
#include <iostream>
#include "Communicator.h"
using namespace std;
enum class EMsgA {
MSG_A_1,
MSG_A_2,
};
enum class EMsgB {
MSG_B_1,
MSG_B_2,
};
struct humaninfo {
std::string name;
std::string sex;
std::string id;
};
class View : public Communicator {
public:
View() {
RegisterMsgHandle(EMsgA::MSG_A_1, [this] { ProcessMSGA1(); });
RegisterMsgHandle(EMsgB::MSG_B_2, [this](const std::any& info) {ProcessMSGB2(info); });
}
void Call() {
Emit(EMsgA::MSG_A_1);
Emit(EMsgB::MSG_B_2, humaninfo{ "flower", "man", "19980323" });
}
private:
void ProcessMSGA1() {
cout << "exec ProcessMSGA1 finished" << endl;
}
void ProcessMSGB2(const std::any& info) {
cout << "exec ProcessMSGB2 finished" << endl;
auto hInfo = std::any_cast<humaninfo>(info);
auto name = hInfo.name;
auto sex = hInfo.sex;
auto id = hInfo.id;
cout << "name = " << name << ", sex = " << sex << ", id = " << id << endl;
}
};
struct Business :Communicator {
Business() {
RegisterMsgHandle(EMsgA::MSG_A_1, [this] {ProcessMSGA1(); });
RegisterMsgHandle(EMsgB::MSG_B_2, [this](const std::any& info) {ProcessMSGB2(info); });
}
void ProcessMSGA1() {
cout << "exec ProcessMSGA1" << endl;
Notify(EMsgA::MSG_A_1);
}
void ProcessMSGB2(const std::any& info) {
auto hInfo = std::any_cast<humaninfo>(info);
auto name = hInfo.name;
auto sex = hInfo.sex;
auto id = hInfo.id;
cout << "exec ProcessMSGB2, name = " << name << ", sex = " << sex << ", id = " << id << endl;
Notify(EMsgB::MSG_B_2, info);
}
};
int main()
{
std::shared_ptr<Communicator> business = std::make_shared<Business>();
std::shared_ptr<View> view = std::make_shared<View>();
business->Add(view);
view->Connect(EMsgA::MSG_A_1, business);
view->Emit(EMsgA::MSG_A_1);
//view->Call();
getchar();
return 0;
}