基于QT的C++中小项目软件开发架构源码

news/2024/11/17 5:47:21/
描述

基于QT信号槽机制实现类之间的交互调用通信,适用于使用不同枚举作为消息交互的类型场景,支持附带任意参数,代码使用方式参考前一篇文章

特性
  1. 代码简洁,不超过100行
  2. 仅需包含一个头文件Communicator.h,需要通信的业务类继承Communicator,界面类继承VCommunicatorMVCommunicator
  3. 消息通过枚举类型定义,支持任意枚举消息,添加枚举无需改动代码。枚举名称区分消息类型,枚举值区分具体消息
  4. 使用便捷,只需注册消息和消息处理函数
  5. 提供发送消息时,附带any传递任意额外信息
  6. 支持多线程,同QT一致的信号槽触发方式,如需控制信号槽触发方式,需要额外补充代码
对比QT信号槽
  1. 适用于使用不同枚举作为消息类型场景,支持附带任意参数
  2. 使用同QT一样便捷,只需注册消息和消息回调函数。且如果使用QT的信号槽进行通信,不同的消息的类型则需要定义多个connect连接函数,如ELoginMsg、ERunMsg,多一种消息就要多一个connect

Communicator.h

#pragma once
#include <QMainWindow>
#include <functional>
#include <unordered_map>
#include <any>class MsgHandler {
public:using MsgID = size_t;using MsgHandleFunc = std::function<void(std::any)>;using MsgHandleFuncNoPara = std::function<void()>;template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>void HandleMsg(T msg, std::any info = std::any{}) {m_msgHandlers[HashID(msg)](info);}
protected:template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>MsgID HashID(T msg) {return std::hash<std::string>()(typeid(T).name()) ^ std::hash<size_t>()(static_cast<size_t>(msg));}template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>void RegisterMsgHandler(T msg, MsgHandleFuncNoPara func) {m_msgHandlers.insert({ HashID(msg),[=] (std::any) { func(); } });}template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>void RegisterMsgHandler(T msg, MsgHandleFunc func) {m_msgHandlers.insert({ HashID(msg), func });}template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>void RemoveMsgHandler(T msg) {if (auto findit = m_msgHandlers.find(msg); findit != m_msgHandlers.end()) {m_msgHandlers.erase(findit);}}
protected:std::unordered_map<MsgID, MsgHandleFunc> m_msgHandlers;
};#define DEF_COMMUNICATOR_CLASS(CommClassName, QtClassName)											\
class CommClassName : public QtClassName, public MsgHandler {										\Q_OBJECT																						\
public:																								\CommClassName(QtClassName* parent = nullptr) : QtClassName(parent) {							\qRegisterMetaType<MsgID>("MsgID");															\qRegisterMetaType<std::any>("std::any");													\}																								\Q_SIGNAL void SendMsg(MsgID, std::any);															\
public:																								\Q_SLOT void HandleHashMsg(MsgID msgID, std::any info = std::any{}) {							\if (bool findHandler = m_msgHandlers.find(msgID) != m_msgHandlers.end()) {					\m_msgHandlers[msgID](info);																\}																							\}																								\
public:																								\template<typename Receiver>																		\void Connect(Receiver* receiver) {																\connect(this, &CommClassName::SendMsg, receiver, &Receiver::HandleHashMsg);					\}																								\template<typename Receiver>																		\void Disconnect(Receiver* receiver) {															\disconnect(this, &CommClassName::SendMsg, receiver, &Receiver::HandleHashMsg);				\}																								\
protected:																							\template<typename T, typename = std::enable_if_t<std::is_enum<T>::value, void>>					\void Emit(T msg, std::any info = nullptr) {														\emit SendMsg(HashID(msg), info);															\}																								\
}				
DEF_COMMUNICATOR_CLASS(Communicator, QObject);
DEF_COMMUNICATOR_CLASS(ViewCommunicator, QWidget);
DEF_COMMUNICATOR_CLASS(MViewCommunicator, QMainWindow);namespace InterConnection {
template<typename Communicator1, typename Communicator2>
void Connect(Communicator1* c1, Communicator2* c2)
{c1->Connect(c2);c2->Connect(c1);
}template<typename Communicator1, typename Communicator2>
void Disconnect(Communicator1* c1, Communicator2* c2)
{c1->Disconnect(c2);c2->Disconnect(c1);
}
}
补充

c++17才支持any,C++17之前的版本可以使用下面的any.h,判断版本大于c++17可以通过宏__cplusplus >= 201703L
any.h

#include <typeinfo>
#include <memory>
namespace std {
class any
{class AnyHelperBase{public:virtual const std::type_info& type()const = 0;virtual AnyHelperBase* clone()const = 0;};template<typename T>class AnyHelper :public AnyHelperBase{public:T data;template<typename ...Args>AnyHelper(Args&&... args) :data(std::forward<Args>(args)...) {}AnyHelper(const AnyHelper& rhs) :data(rhs.data) {}AnyHelper(const T& value) :data(value) {}virtual const std::type_info& type() const{return typeid(T);}virtual AnyHelper* clone() const{return new AnyHelper(*this);}};template<typename T>friend T any_cast(const any& a);
private:std::unique_ptr<AnyHelperBase> pdata{};
public:any() :pdata(nullptr) {}template<typename T>any(T&& value) : pdata(new AnyHelper<std::decay_t<T>>(value)) {}any(const any& rhs) {if (rhs.pdata != nullptr) {pdata.reset(rhs.pdata->clone());}}any(any& rhs) {if (rhs.pdata != nullptr) {pdata.reset(rhs.pdata->clone());}}any(any&& rhs) :pdata(rhs.pdata.release()) {}const std::type_info& type() const{return pdata->type();}bool has_value() const {return pdata != nullptr;}void reset() {pdata.reset();}template<typename T>any& operator=(T value) {pdata.reset(new AnyHelper<std::decay_t<T>>(value));return *this;}any& operator=(any rhs){pdata.reset(rhs.pdata->clone());return *this;}
};template<typename T>
T any_cast(const any& a)
{auto p = dynamic_cast<any::AnyHelper<std::decay_t<T>>*>(a.pdata.get());if (p == nullptr)throw std::runtime_error("Bad any cast!");return p->data;
}
}

由于现在大多软件都是基于QT进行开发,QT又有自己的事件循环机制,所以只能进行拓展通信方式,也可以自行实现事件循环,但是跟QT结合起来用就不太适宜了。


http://www.ppmy.cn/news/1531317.html

相关文章

Java设计模式——工厂模式扩展

5.5 工厂模式扩展 简单工厂配置文件解除耦合 &#xff08;spring底层类似&#xff09; 可以通过工厂模式配置文件的方式解除工厂对象和产品对象的耦合。在工厂类中加载配置文件中的全类名&#xff0c;并通过反射技术创建对象进行存储&#xff0c;客户端如果需要对象&#xf…

2024云手机推荐与排行:怎样选择最适合的云手机?

在当前市场上&#xff0c;云手机品牌繁多&#xff0c;既有老牌的稳定产品&#xff0c;也有新晋品牌异军突起。不同品牌的云手机因其配置、性能、功能等方面的差异&#xff0c;带来的使用体验也各不相同。为了帮助大家更好地挑选适合的云手机设备&#xff0c;我们将对市面上一些…

ubuntu如何开启和关闭图形界面

在Ubuntu中&#xff0c;你可以根据需要开启或关闭图形界面。以下是具体的方法&#xff1a; 关闭图形界面 方法一&#xff1a;使用 systemctl 命令 打开终端。输入以下命令切换到多用户目标&#xff08;相当于关闭图形界面&#xff09;&#xff1a;sudo systemctl set-defaul…

JAVA零基础入门——高级教程之集合框架

目录 1. 关于集合框架 1.1 集合接口 1.2 集合实现类 1.3 集合算法及迭代器和比较器的使用 2. 数据结构 2.1 ArrayList 2.2 LinkedList 2.3 HashMap 2.4 HashSet 3. 迭代器 1. 关于集合框架 集合框架是使用数据结构&#xff08;参见本文2. 数据结构&#xff09;来满…

机器学习笔记(一)初识机器学习

1.定义 机器学习是一门多学科交叉专业&#xff0c;涵盖概率论知识&#xff0c;统计学知识&#xff0c;近似理论知识和复杂算法知识&#xff0c;使用计算机作为工具并致力于真实实时的模拟人类学习方式&#xff0c;并将现有内容进行知识结构划分来有效提高学习效率。 机器学习有…

el-table+el-form实现表单校验和解决不垂直居中导致的问题

el-tableel-form实现表单校验 1.实现el-table的表单校验 关键点123 2.解决不垂直居中导致的问题 问题效果图 解决方案 .item-align-center {display: inline-flex; }

node - npm常用命令和package.json说明

NPM NPM 的全称是 Node Package Manager&#xff0c;是随同 NodeJS 一起安装的包管理和分发工具&#xff0c;它很方便让 JavaScript 开发者下载、安装、上传以及管理已经安装的包。 官网 npm 官网&#xff1a;https://www.npmjs.com npm 命令 命令说明npm init初始化一个新的…

ubuntu下检查端口是否占用问题,编写shell脚本检查端口是否占用

1.创建脚本 touch check_port.sh2.粘贴以下内容到check_port.sh中 #!/bin/bash# 检查端口是否被占用的函数 check_port() {local port80local result$(sudo lsof -i:$port)if [[ -z "$result" ]]; thenecho "端口 $port 未被占用。"elseecho "警告:…