纯cpp如何模拟qt的信号与槽
- 我之前是如何使用bind的?
- 一.demo示例
- 二.简单来讲,c++自带的bind与function函数,如何实现类似信号与槽的机制
- 1. 简单语法
- 2. function与bind联动
- 尝试1
- 尝试2
- 真正实现
- 流程图
- 自我反思
我之前是如何使用bind的?
一.demo示例
using MsgHander = std::function<void(const TcpConnectionPtr &conn, json &js, Timestamp)>;
unordered_map<int, MsgHander> _msgHandlerMap; // 消息id对应的处理操作/*****************************************/
// 注册消息回调_server.setMessageCallback(std::bind(&ChatServer::onMessage, this, _1, _2, _3));
/*****************************************/// 注册消息以及对应的Handler回调函数
ChatService::ChatService()
{_msgHandlerMap.insert({LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3)});_msgHandlerMap.insert({REG_MSG, std::bind(&ChatService::reg, this, _1, _2, _3)});_msgHandlerMap.insert({ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, _1, _2, _3)});_msgHandlerMap.insert({ADD_FRIEND_MSG, std::bind(&ChatService::addFirend, this, _1, _2, _3)});// 群组业务管理相关事件处理回调注册_msgHandlerMap.insert({CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, _1, _2, _3)});_msgHandlerMap.insert({ADD_GROUP_MSG, std::bind(&ChatService::addGroup, this, _1, _2, _3)});_msgHandlerMap.insert({GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, _1, _2, _3)});
}/*****************************************/// 上报读写时间相关信息的回调函数
void ChatServer::onMessage(const TcpConnectionPtr &conn, Buffer *buffer, Timestamp time)
{string buf = buffer->retrieveAllAsString();// 数据的反序列化json js = json::parse(buf);// 达到的目的:完全解耦网络模块的代码和业务模块的代码// 通过js["msgid"]获取->业务的hander->conn js timeauto msgHandler = ChatService::instance()->getHandler(js["msgid"].get<int>());// 回调消息绑定好的事件处理器,并执行相应的业务处理msgHandler(conn, js, time);
}/*****************************************/// 获取消息对应的处理器
MsgHander ChatService::getHandler(int msgid)
{// 记录错误日志,msgid没有对应的事件处理回调auto it = _msgHandlerMap.find(msgid);if (it == _msgHandlerMap.end()){// 返回一个默认的处理器,是一个空操作return [=](const TcpConnectionPtr &conn, json &js, Timestamp){LOG_ERROR<< "Can not find handler:[" << msgid << "]!";};}else{return _msgHandlerMap[msgid];}
}
二.简单来讲,c++自带的bind与function函数,如何实现类似信号与槽的机制
1. 简单语法
#include <functional>
#include <iostream>void print(int arg)
{std::cout<<arg<<std::endl;
}void add(int a, int b)
{std::cout<< a+b <<std::endl;
}int cut(int a , int b)
{return a - b;
}class A{
public:int number;A(){}A(int num){number = num;}Add(int a,int b);
private:};
A::Add(int a , int b)
{return a + b ;
}int main()
{/******************** 用法一 :* 相当于给一个(返回值是void,参数是int)的函数起一个(别名)* **********************/std::function<void(int)> myPrint = print;myPrint(100);/*****************************************/std::function<void(int,int)> myAdd = add;myAdd(1,2);/****************************************/std::function<int(int,int)> myCut = cut;int ret = myCut(1,2);std::cout<<ret<<std::endl;/****************************************//********* 用法二:* *************/A a1;std::function<int(A&,int,int)> func_ref_add = &A::Add;ret = func_ref_add(a1,3,4);std::cout<<ret<<std::endl;/****************************************************/const A a2(999);std::function<int (A const&)>class_number_call = &A::number;ret = class_number_call(a2);std::cout<<ret<<std::endl;return 0;
}
2. function与bind联动
#include<iostream>
#include<functional>using namespace std;
using namespace std::placeholders;int add(int a,int b)
{return a + b;
}class A{
public:int number;A(){}A(int num){number = num;}Add(int a,int b);
private:};
A::Add(int a , int b)
{return a + b ;
}int main()
{int ret = add(1,1);cout<<ret<<endl;/********* std::placeholders::_1相当于一个占位符,* 如果调用func_add_1只用调用一个参数了,另一个参数是5* ******/function<int(int)>func_add_1 = bind(add,std::placeholders::_1,5);ret = func_add_1(3);cout<<ret<<endl;/********** 还可以直接使用auto* ***************/auto func_add_2 = bind(add,std::placeholders::_1,5);ret = func_add_2(4);cout<<ret<<endl;A classA;//A类的方法,A类的对象,该函数的一些参数设置...auto member_func_bind = std::bind(&A::Add,&classA,std::placeholders::_1,66);ret = member_func_bind(34);cout<<ret<<endl;return 0;
}
尝试1
#include <iostream>
#include <functional>
#include <map>/*** @brief Signal 类充当信号的角色* */
class Signal {
public:using Slot = std::function<void()>;/** 连接* signalName 信号 const std::string&* slot 槽 const std::function<void()>&* 注意:signalName和slot根据根据需求设计出任何函数类型*/void connect(const std::string& signalName, const Slot& slot) {slots_[signalName] = slot;}void emit(const std::string& signalName) {auto it = slots_.find(signalName);if (it != slots_.end()) {it->second();//找到对应的函数,并调用} else {std::cerr << "Signal not connected: " << signalName << std::endl;}}private:std::map<std::string, Slot> slots_;
};/*** @brief Object 类包含了槽函数。* */
class Object {
public:Signal signal;void slot1() {std::cout << "Slot 1 called" << std::endl;}void slot2(int value) {std::cout << "Slot 2 called with value: " << value << std::endl;}
};int main() {Object obj;// Connect slots to signalsobj.signal.connect("signal1", std::bind(&Object::slot1, &obj));obj.signal.connect("signal2", std::bind(&Object::slot2, &obj, 42));// Emit signalsobj.signal.emit("signal1");obj.signal.emit("signal2");return 0;
}
/*
* Slot 1 called
* Slot 2 called with value: 42
*/
尝试2
#include <iostream>
#include <string>
#include <map>class Signal {
public:std::string data;std::map<std::string, std::string> parameters;
};class Chatbot {
public:void getResponse(const Signal& signal) {// Access data and parameters from the signalstd::string data = signal.data;std::map<std::string, std::string> parameters = signal.parameters;// Process the signal and provide a response// ...std::cout << "Received signal with data: " << std::endl << data << std::endl;std::cout << "Parameters: " << std::endl;for (const auto& pair : parameters) {std::cout << pair.first << ": " << pair.second << std::endl;}}
};int main() {// Create a signal with data and parametersSignal signal;signal.data = "Hello";signal.parameters["param1"] = "10";signal.parameters["param2"] = "value";// Pass the signal to the chatbotChatbot chatbot;chatbot.getResponse(signal);return 0;
}
/*
Received signal with data:
Hello
Parameters:
param1: 10
param2: value
*/
真正实现
/**********************上面两个方法只是尝试,现在是真正的实现,如下:************************/
/*** 逻辑:* bind绑定 (string 标识符,信号)* emit 标识符 -> 槽函数(信号)*/
#include <iostream>
#include <functional>
#include <map>/*** @brief Signal 类充当信号的角色* */
class Signal {
public:using SIGNAL = std::function<void()>;/** 连接* signalName 信号标识符 const std::string&* slot 信号函数 const std::function<void()>&* 注意:signalName和slot根据根据需求设计出任何函数类型*/void connect(const std::string& signalName, const SIGNAL& signal) {signals_[signalName] = signal;}void emit(const std::string& signalName) {auto it = signals_.find(signalName);if (it != signals_.end()) {it->second();//通过对应信号标识符 调用 信号函数} else {std::cerr << "Signal not connected: " << signalName << std::endl;}}private:std::map<std::string, SIGNAL> signals_;
};/*** @brief Object 类包含了槽函数。* */
class Object {
public:Signal signal;void slot1() {//槽函数1std::cout << "Slot 1 called" << std::endl;}void slot2(int value) {//槽函数1std::cout << "Slot 2 called with value: " << value << std::endl;}void signal1() {//信号函数1this->slot1();}void signal2(int value) {//信号函数2this->slot2(value);}
};int main() {Object obj;// Connect slots to signalsobj.signal.connect("signal1", std::bind(&Object::signal1, &obj));//bind就是对象方法+对象实例obj.signal.connect("signal2", std::bind(&Object::signal2, &obj, 42));// Emit signalsobj.signal.emit("signal1");obj.signal.emit("signal2");return 0;
}
/*
Slot 1 called
Slot 2 called with value: 42
*/
流程图
自我反思
模拟qt的信号与槽就是在信号的部分多进行一步封装,可以分为三层:信号标识符,信号函数与槽函数,信号标识符可以是int,也可以是string。通过信号标识符与信号函数进行连接,然后通过信号标识符找到信号函数,再使用信号函数调用槽函数。
为什么不直接用信号标识符连接槽函数?因为信号标识符无法携带任何参数,而信号函数可以,我们然后通过信号函数的参数再去调用槽函数,这样就对应了qt的机制emit函数,其实就是:在已经注册了的“信号与槽”中寻找对应的信号标识符,然后再通过map映射找到信号函数,然后调用信号函数,信号函数再去调用槽函数,这样就形成了一个闭环