纯cpp如何模拟qt的信号与槽
纯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 time
auto 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 signals
obj.signal.connect("signal1", std::bind(&Object::slot1, &obj));
obj.signal.connect("signal2", std::bind(&Object::slot2, &obj, 42));
// Emit signals
obj.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 signal
std::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 parameters
Signal signal;
signal.data = "Hello";
signal.parameters["param1"] = "10";
signal.parameters["param2"] = "value";
// Pass the signal to the chatbot
Chatbot 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() {//槽函数1
std::cout << "Slot 1 called" << std::endl;
}
void slot2(int value) {//槽函数1
std::cout << "Slot 2 called with value: " << value << std::endl;
}
void signal1() {//信号函数1
this->slot1();
}
void signal2(int value) {//信号函数2
this->slot2(value);
}
};
int main() {
Object obj;
// Connect slots to signals
obj.signal.connect("signal1", std::bind(&Object::signal1, &obj));//bind就是对象方法+对象实例
obj.signal.connect("signal2", std::bind(&Object::signal2, &obj, 42));
// Emit signals
obj.signal.emit("signal1");
obj.signal.emit("signal2");
return 0;
}
/*
Slot 1 called
Slot 2 called with value: 42
*/
流程图
自我反思
模拟qt的信号与槽就是在信号的部分多进行一步封装,可以分为三层:信号标识符,信号函数与槽函数,信号标识符可以是int,也可以是string。通过信号标识符与信号函数进行连接,然后通过信号标识符找到信号函数,再使用信号函数调用槽函数。
为什么不直接用信号标识符连接槽函数?因为信号标识符无法携带任何参数,而信号函数可以,我们然后通过信号函数的参数再去调用槽函数,这样就对应了qt的机制emit函数,其实就是:在已经注册了的“信号与槽”中寻找对应的信号标识符,然后再通过map映射找到信号函数,然后调用信号函数,信号函数再去调用槽函数,这样就形成了一个闭环