C++自动注册的工厂与--whole-archive

上篇文章《静态库和链接选项--whole-archive》提到--whole-archive的一个应用场景:C++自动注册的工厂,这篇文章来填坑。

预备知识

我们经常用工厂类或工厂方法统一管理资源,实现资源的创建和使用之间的解耦,调用者无需关心资源创建的细节,直接到工厂申请创建好的资源即可。

一般情况下,资源提供了统一的接口供使用者调用,到工厂的获取也采用统一的方式,极大地简化了编码,尤其是资源创建比较繁冗的情况下。而且,资源统一管理也从某种程度上节省了资源的重复创建带来的开销。

下面是典型的工厂函数的实现:

using MsgHandler = std::function<bool(std::string_view msg_data)>;MsgHandler* 
create_msg_handler(std::string_view msg_type) {if (msg_type == "account") {return http_handle;}else if (msg_type == "pay") {return json_handle;}...else {return nullptr;} 
}

这个函数根据不同的消息类型返回对应的消息处理器,基本实现了工厂的注册功能。

我们看下这种实现的问题。假设新增一种消息处理器,我们需要添加一个if语句,然后返回对应的handler,看似问题不大。但只要有修改就会容易出错,甚至不经意的触碰到键盘的一个键都会引入一个bug(这种情况真遇到过),所以我们的原则应该是尽量不要动已有的代码,要从代码结构的扩展性上下手。

自动注册的工厂模式可以解决这个问题。

自动注册的工厂模式

.
├── msg_handler.cpp
├── msg_handler.h
├── pay_handler.cpp
├── CMakeLists.txt
└── main.cpp

其中msg_handler.[cpp|h]实现工厂,pay_handler.cpp提供了pay类型消息的处理器,main.cpp演示了消息处理器的使用。

// msg_handler.h
#include <functional>
#include <string_view>using MsgHandler = std::function<bool(std::string_view msg_data)>;// 注册消息处理器 
void 
register_msg_handler(const char *msg_type, MsgHandler handler);// 获取指定消息类型的处理器
MsgHandler* 
get_msg_handler(const char *msg_type);// msg_handler.cpp
#include <map>
#include <string>#include "msg_handler.h"static std::map<std::string, MsgHandler>& 
get_map() {static std::map<std::string, MsgHandler> map_handlers;return map_handlers;
}void 
register_msg_handler(const char *msg_type, MsgHandler handler) {get_map()[msg_type] = handler;
}MsgHandler* 
get_msg_handler(const char *msg_type) {auto& m = get_map();auto it = m.find(msg_type);if (it != m.end()) {return &it->second;}else {return nullptr;}
}

msg_handler实现了简单的工厂,调用register_msg_handler注册处理器,使用方调用get_msg_handler获取消息处理器。工厂内部使用map存储,k为消息类型,v为消息处理器。注意,map_handlers为函数内部的静态变量,之所以这么做,主要是给这个静态变量确定的初始化顺序:首次调用get_map时初始化,保证在注册的时候map_handlers一定是可用的。

下面是pay_handler.cpp文件,实现了pay类型的消息处理及自动注册。

#include "msg_handler.h"
#include <stdio.h>class PayHandler {public:PayHandler() { register_msg_handler("pay", PayHandler::handle);}static bool handle(std::string_view msg_data) {printf("pay handle\n");return true;}
};static PayHandler pay_handler;

实现自动注册的逻辑分两步:

  1. PayHandler构造函数实现消息处理器的注册;

  2. 声明一个文件作用域的变量,初始化时自动调用构造函数,从而实现了自动注册。

pay_handler采用全局变量也是可以的,这里加了static主要为了避免这个变量作用域过大被滥用。

下面是主流程的实现:main.cpp

#include "msg_handler.h"
#include <stdio.h>int main() {MsgHandler* handle = get_msg_handler("pay");if (handle) {(*handle)("test data");}else {printf("not found\n");}return 0;
}

程序正常运行会输出:pay handle,大家可以先试着编译运行下,看看输出结果。

编译运行

如果没有把pay_handler源代码的目标文件链接到可执行文件,会输出not found,因为工厂里没有找到对应的handler。

大概率编译的指令跟下面的大同小异,这里只是简单的使用了-l链接libpayhandler静态库,然而链接器最终会优化掉,因为通过main.cpp链接器仅能判断出依赖libmsghandler库。

$ g++ -c msg_handler.cpp
$ g++ -c pay_handler.cpp
$ ar rcs libmsghandler.a msg_handler.o
$ ar rcs libpayhandler.a pay_handler.o
$ g++ main.cpp -L. -lpayhandler -lmsghandler -o main
$ ./main
not found

这种情况需要使用--whole-archive选项强制将库链接进可执行文件,修改为:

g++ main.cpp -L. \-Wl,--whole-archive -lpayhandler \-Wl,--no-whole-archive \-lmsghandler -o main

在这个例子里--whole-archive是必需的,我们无法像上篇文章那样简单的调整一下main.cpp和静态库的位置解决问题,貌似也没其他方法了。

附上对应的CMakeLists.txt文件:

cmake_minimum_required (VERSION 3.24.0)
project(main)add_library(payhandler STATIC pay_handler.cpp)
add_library(msghandler STATIC msg_handler.cpp)add_executable(${PROJECT_NAME} main.cpp)target_link_libraries(${PROJECT_NAME}msghandler"$<LINK_LIBRARY:WHOLE_ARCHIVE,payhandler>"
)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/139843.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于springboot乐器视频学习网站设计与实现(源码齐全可用)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。你想解决的问题&#xff0c;今天给大家介绍…

卡码网57. 爬楼梯

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬至多m (1 < m < n)个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 注意&#xff1a;给定 n 是一个正整数。 输入描述 输入共一行&#xff0c;包含两个正整数&#xff0c;分别表示n, …

国际阿里云:Linux实例负载高问题排查和异常处理!!!

问题描述 在您使用ECS实例过程中&#xff0c;可能会遇到实例系统负载较高的情况&#xff0c;负载过高&#xff0c;可能会引发一系列异常问题&#xff0c;简单说您如下&#xff1a; CPU使用率或负载过高&#xff1a;一般来说&#xff0c;当CPU使用率≥80%时&#xff0c;定义为C…

PROFINET和UDP、MODBUS-RTU通信速度对比实验

这篇博客我们介绍PROFINET 和MODBUS-RTU通信实验时的数据刷新速度,以及这种速度不同对控制系统带来的挑战都有哪些,在介绍这篇对比实验之前大家可以参考下面的文章链接: S7-1200PLC和SMART PLC的PN智能从站通信 S7-200 SMART 和 S7-1200PLC进行PROFINET IO通信-CSDN博客文…

【Redis系列】Redis的核心命令(上)

哈喽&#xff0c;大家好&#xff0c;我是小浪。那么上篇博客教会了大家如何在Linux上安装Redis&#xff0c;那么本篇博客就要正式开始学习Redis啦&#xff0c;跟着俺的随笔往下看~ 1、启动Redis 那么如何启动Redis呢&#xff1f;最常用的是以下这个命令&#xff1a; redis-cl…

jedis、lettuce与redis交互分析

概念梳理&#xff1a; redis是缓存服务器&#xff0c;jedis、lettuce都是Java语言下的redis客户端&#xff0c;用于与redis服务器进行交互。springboot项目中一般使用的是spring data redis&#xff0c;spring data redis依赖与jedis或lettuce&#xff0c;可以进行配置&#x…

物联网AI MicroPython学习之语法 network网络配置模块

学物联网&#xff0c;来万物简单IoT物联网&#xff01;&#xff01; network介绍 模块功能&#xff1a; 用于管理Wi-Fi和以太网的网络模块参考用法&#xff1a; import network import time nic network.WLAN(network.STA_IF) nic.active(True) if not nic.isconnected():…

要找事做,我真怕被闲死

要找事做&#xff0c;我真怕被闲死 | 昨晚睡足了5个多小时&#xff0c;元气开始恢复&#xff0c;今晨6点钟醒来&#xff0c;如厕后无睡意&#xff0c;便窝在被子里闭目养神&#xff0c;同时想心事。 7点钟翻身起床穿衣洗刷后&#xff0c;烧瓶开水泡杯浓茶&#xff0c;打开电脑…

无人机航迹规划:五种最新智能优化算法(GRO、SWO、COA、LSO、KOA)求解无人机路径规划MATLAB

一、五种算法&#xff08;GRO、SWO、COA、LSO、KOA&#xff09;简介 1、淘金优化算法GRO 淘金优化算法&#xff08;Gold rush optimizer&#xff0c;GRO&#xff09;由Kamran Zolf于2023年提出&#xff0c;其灵感来自淘金热&#xff0c;模拟淘金者进行黄金勘探行为。VRPTW&am…

idea配置tomcat参数,防止nvarchar保存韩文、俄文、日文等乱码

描述下我的场景&#xff1a; 数据库服务器在远程机器上&#xff0c;数据库使用的Oracle&#xff0c;字符集是ZHS16GBK&#xff0c;但保存韩文、俄文、日文等字段A的数据类型是nvarchar(120)&#xff0c;而nvarchar使用的是Unicode 编码&#xff0c;有点乱。。 遇到的问题&…

Thinkphp8 - 连接多个数据库

// 数据库连接配置信息connections > [mysql > [// 数据库类型type > mysql,// 服务器地址hostname > 127.0.0.1,// 数据库名database > thinkphp,// 用户名username > env(DB_USER, root),// 密码password >…

Java图像编程之:Graphics

一、概念介绍 1、Java图像编程的核心类 Java图像编程的核心类包括&#xff1a; BufferedImage&#xff1a;用于表示图像的类&#xff0c;可以进行像素级的操作。Image&#xff1a;表示图像的抽象类&#xff0c;是所有图像类的基类。ImageIcon&#xff1a;用于显示图像的类&a…

3、FFmpeg基础

1、FFmpeg 介绍 FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库。 2、FFmpeg 组成 - libavformat:用于各种音视频[封装…

敏捷开发是什么?敏捷开发流程是怎么样的?

1. 什么是敏捷开发&#xff1f; 敏捷开发是一种迭代、增量式的软件开发方法&#xff0c;旨在通过灵活、协作和快速响应变化的方式&#xff0c;提高开发团队的效率和产品的质量。相较于传统的瀑布式开发模型&#xff0c;敏捷开发更加注重用户需求的响应和团队协作&#xff0…

tomcat+idea--如何在idea上发布项目

对应于idea2022以后的版本 &#xff08;一&#xff09;如何配置idea上的tomcat&#xff1f; 1、新建一个项目&#xff0c;左上角File&#xff0c;new&#xff0c;project&#xff0c;新建后就和普通的java项目一样。 2、然后点击项目名&#xff0c;右键选择“Add framework s…

JUL日志

文章目录 JUL日志JUL日志讲解Properties配置文件编写日志配置文件Lombok快速开启日志Mybatis日志系统 JUL日志 如果使用System.out.println来打印信息&#xff0c;项目中存在大量的控制台输出语句&#xff0c;会显得很凌乱&#xff0c;而且日志的粒度是不够细的&#xff0c;假…

汽车ECU的虚拟化技术初探(二)

目录 1.概述 2.U2A虚拟化方案概述 3.U2A的虚拟化功能概述 4.虚拟化辅助功能的使能 5.留坑 1.概述 在汽车ECU的虚拟化技术初探(一)-CSDN博客里&#xff0c;我们聊到虚拟化技术比较关键的就是vECU的虚拟地址翻译问题&#xff0c;例如Cortex-A77就使用MMU来进行虚实地址的转换…

C语言——贪吃蛇

一. 游戏效果 贪吃蛇 二. 游戏背景 贪吃蛇是久负盛名的游戏&#xff0c;它也和俄罗斯⽅块&#xff0c;扫雷等游戏位列经典游戏的⾏列。 贪吃蛇起源于1977年的投币式墙壁游戏《Blockade》&#xff0c;后移植到各种平台上。具体如下&#xff1a; 起源。1977年&#xff0c;投币式…

KDE Plasma 6 将不支持较旧的桌面小部件

KDE Plasma 6 进行了一些修改&#xff0c;需要小部件作者进行调整。开发人员&#xff0c;移植时间到了&#xff01; KDE Plasma 6 是备受期待的桌面环境版本升级版本。 最近&#xff0c;其发布时间表公布&#xff0c;第一个 Alpha 版本将于 2023 年 11 月 8 日上线&#xff0…

Elasticsearch概述

Elasticsearch&#xff08;ES&#xff09;是一个基于Lucene库构建的开源搜索引擎。它提供了一个分布式、多租户的全文搜索引擎&#xff0c;具有RESTful Web界面。Elasticsearch的主要特点包括&#xff1a; 分布式搜索引擎&#xff1a; Elasticsearch被设计为一个分布式系统&…