《C++API设计》读书笔记(3):模式
本章内容
本章涵盖了一些与C++API设计相关的设计模式和惯用法。
“设计模式(Design Pattern)”表示软件设计问题的一些通用解决方案。该术语来源于《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Software)
本书不会涵盖所有模式,只讨论一些和API设计有关的。
 还会涉及一些C++的惯用法,它们并非是真正的通用设计模式,但却是C++API设计的重要技巧。
 本章讨论的技巧如下:
1. Pimpl
Pimpl 意思是 Pointer to Implementation,指向实现的指针。
 (Pimpl 不是严格意义上的“设计模式”,而是受制于C++语法特定限制的变通方案,可以看作是 桥接 设计模式的一种特例)
它主要解决的是C++语法中的一个问题:类的private成员其实只在内部使用,但是在定义时还需要写在.h文件中公开。因此,Pimpl 的做法是只在.h中定义一个指向实现的指针,那就可以将一些private成员放在.cpp中的实现中,这样就在.h文件中隐藏了private成员。
 举例:
 
使用Pimpl的方法
举例代码:
 .h文件中:
class AutoTimer
{
public:explicit AutoTimer();~AutoTimer();
private:class Impl;Impl* mImpl;
}
 
随后,在.cpp中可以定义Impl类并实现具体的逻辑。
一个值得考虑的设计问题是:Impl类中放置多少逻辑?有以下选择:
- 仅私有变量
 - 私有变量+私有方法
 - 公有类的所有方法。(而共有类的方法只是对
Impl类中方法的简单包装) 
每种选择都适应于不同情况。一般情况下推荐 2 。
另外,使用 Pimpl 时需要注意:
- virtual 函数不能放在
Impl类中,否则公有类的子类无法继承。 Impl类可能还需要一个公有类的指针以方便其调用公有类的方法。
使用Pimpl的类的复制
使用 Pimpl 的类无法进行默认的复制,因为复制出的对象会和原对象指向同一个Impl类变量。
 这个问题有两种方法解决:
- 禁止复制
 - 显示定义复制语义
 
Pimpl与智能指针
使用 Pimpl 时容易犯错的一点是:构造时忘记分配它,析构时忘记销毁它。
 为此,可以采用 智能指针 或者 作用域指针。
Pimpl的优点
- 信息隐藏。
 - 降低耦合
 - 加速编译
 - 更好的二进制兼容性。(就算
Impl类实现发生变化,公用类的对象也不会改变二进制数据) - 惰性分配(可以选择只在需要时分配)
 
Pimpl的缺点
- 额外的分配与销毁
Impl类对象会增加性能开销。 - 给开发者带来了不便:很多函数调用时需要加上
mImpl->,如果Impl类需要调用公有类的方法也需要通过指针。 - 编译器不能再检查const:
Impl类的成员更改,公有类的const无法检查出。 
2. 单例
“单例”设计模式确保一个类仅存在一个实例,并提供唯一的全局访问点。
它可以看作是一种更优雅的全局变量,但是相比全局变量有一些优点:
- 确保这个类只能创建一个实例。
 - 控制对象的分配与销毁。
 - 可以支持线程安全。
 - 避免污染全局命名空间。
 
其基本实现很简单,而本篇重点讨论的是:
- 如何更健壮地实现。
 - 它也有些缺点,但很多人都有滥用单例的趋向。为此这里也提供一些替代方法。
 
在C++中实现单例
其基本实现很简单:
class Singleton 
{
public:static Singleton &GetInstance();
};
 
Singleton &Singleton::GetInstance()
{static Singleton instance;return instance;
}
 
有一些做法可以增加健壮性:
- 声明私有默认构造函数:防止用户创建新的实例。
 - 声明私有复制构造函数和赋值操作:防止用户复制。
 - 声明私有析构函数:防止用户删除。
 GetInstance()返回引用而非指针:防止用户删除。
单例的线程安全
上面的 GetInstance() 并非线程安全的。
常规的处理方式是加互斥锁。但这样会增加开销。
 要优化此类激进的加锁行为,可以采用DCLP(Double Check Locking Pattern),即加互斥锁前先判断instance是否存在。
 但是DCLP不能保证任何编译器和处理器下都能正常工作。
所以,也许你不应该尝试保证GetInstance()是线程安全的(毕竟对使用C++这样对并发缺乏内在支持的语言来说,实现线程安全总会遇到这些困难)。如果你真的需要它线程安全并且性能最高,可以考虑避免惰性实例化模型(即不要在需要他时再实例化),比如:
- 静态初始化:在cpp文件中main函数调用之前调用
GetInstance()。 - 显式API初始化:在一开始就调用
GetInstance(),调用时可以加互斥锁。而GetInstance()的内部就不用加互斥锁了。 
替代方案:依赖注入
初始化时传入需要的实例的指针,而不是内部再使用GetInstance()获得实例。
替代方案:单一状态
假设状态的初始化不需要控制,或者不需要使用单例对象存储,那么就可以使用“单一状态”,即:
 类本身不保持是单例,但是其所有成员(或者说“状态”)都是static。
替代方案:会话上下文
《设计模式》作者指出,单例有可能导致拙劣的设计。使用时候需要思考,“单例”是否真的是正确的模式?
需求是会变的,未来有些对象可能会需要支持多个实例。
因此,需要尽早考虑引入 “会话(session)” 或 “上下文(context)” 的概念。这是在强调:使用单一的实例维护所有相关的状态,而非使用多个单例。
3. 工厂模式
工厂模式是关于创建的设计模式,本质上是构造函数的泛化,可以回避C++构造函数的限制:
- 没有返回值。这样无法返回错误等其他信号。
 - 命名限制。这样相同参数的构造函数只能有一个。
 - 静态绑定创建。这样无法在运行时动态决定类型。
 - 不允许虚构造函数。限制同上。
 
从使用层面上看,工厂方法仅是一个普通的方法,调用时返回对应类的实例:
class RendererFactory
{
public:IRenderer* CreateRenderer(string type)
}
 
但这里的 IRenderer 是一个抽象基类(Abstract Base Class,简称ABC)。抽象基类是包含纯虚函数的类,不能被实例化。(另外要注意,抽象基类的析构函数需要声明为虚的)。
这样,用户可以使用参数动态决定要创建的类型。
另外作为扩展,可以让工厂类提供注册函数,这样用户可以自己添加新的类型
static void RegisterRender(string type, CreateCallback cb);
 
4. API包装器
基于另一组API来包装接口是一项常见的API设计任务。比如,你在维护一个遗留的代码库,相比重构代码,你更愿意封装一套新的,更简洁的API,以隐藏所有的底层遗留代码。
下面,按照包装器层和原始接口的差异程度递增地划分:
4.1 代理(Proxy)
一对一地,将函数调用转发到具有相同形式的另一个接口。
案例:
- 实现原始对象的惰性实例。
 - 实现对原始对象的访问控制。
 - 支持 “调试” 模式或者 “演习(DryRun)” 模式。
 - 保证原始对象的线程安全
 - 可以让多个代理对象共享相同的原始对象。
 - 应对原始对象将来被修改的情况。
 
4.2 适配器
一对一地,将接口转换为一个兼容但是不完全相同的另一个接口。
优点:
- 可以转换数据类型。
 - 强制API始终保持一致性。
 - 包装API的依赖库
 
4.3 外观模式
外观模式能够为一组类提供简化的接口。它实际上定义了一个更高层次的接口,使得底层类更易于使用且对用户隐藏。
 (一个例子:用一个“酒店助手类”简化了“预定房间”、“预定晚餐”、“预定出租车”等事务。)
用途:
- 隐藏遗留代码。
 - 创建更便捷的API。
 - 支持简化功能或替代功能的API。
 
5. 观察者模式
观察者模式为了解决这样的问题:
 实现复杂的任务通常需要多个对象一起合作完成。为了让A可以调用B,较为简单的方法就是A.cpp包含B.h,但是这样产生了编译时依赖,迫使想要复用A时也必须引入B。
观察者模式就是 “发布/订阅” 范式的一个具体实例。
实现观察者模式的典型做法是引入两个概念:
- Subject,主体,也就是发布者。
 - Observer,观察者,也就是订阅者。
 
代码上,Subject仅知道Observer接口类即IObserver,他将维护IObserver列表
class IObserver
{virtual void Update() = 0;
}class ISubject
{
std::vector<IObserver*> ObserverList
}
 
随后,观察者通过继承IObserver来实现观察者对象。
然后,在使用时,Subject就可以订阅(Subscribe)若干Observer,并在需要的时候通知(Notify)它们。
这样,Subject和Observer就没有编译时依赖关系,它们的关系是运行时动态创建的。
相关文章:
《C++API设计》读书笔记(3):模式
本章内容 本章涵盖了一些与CAPI设计相关的设计模式和惯用法。 “设计模式(Design Pattern)”表示软件设计问题的一些通用解决方案。该术语来源于《设计模式:可复用面向对象软件的基础》(Design Patterns: Elements of Reusable Object-Oriented Softwar…...
小程序搜索词优化:小陈运营的秘密武器
大家好,我是小陈,今天要和大家分享一下小程序搜索词优化的经验和技巧。在数字化时代,小程序已经成为许多企业的重要工具,但要让小程序在竞争激烈的市场中脱颖而出,搜索词优化是不可或缺的一环。在本文中,我…...
SpringSecurity 入门
文章目录 Spring Security概念快速入门案例环境准备Spring配置文件SpringMVC配置文件log4j配置文件web.xmlTomcat插件 整合SpringSecurity 认证操作自定义登录页面关闭CSRF拦截数据库认证加密认证状态记住我授权注解使用标签使用 Spring Security概念 Spring Security是Spring…...
【每日一题Day335】LC1993树上的操作 | dfs
树上的操作【LC1993】 给你一棵 n 个节点的树,编号从 0 到 n - 1 ,以父节点数组 parent 的形式给出,其中 parent[i] 是第 i 个节点的父节点。树的根节点为 0 号节点,所以 parent[0] -1 ,因为它没有父节点。你想要设计…...
FPGA:卷积编码及维特比译码仿真
FPGA:卷积编码及维特比译码仿真 本篇记录一下在FPGA中完成卷积编码和维特比译码的过程,通过代码解释编码的过程和译码的过程,便于理解,同时也方便移植到其他工程中。 1. 准备工作 卷积编译码IP核—convolutionIP核和viterbiIP核…...
MySQL学习笔记4
客户端工具的使用: MySQL: mysql命令行工具,一般用来连接访问mysql的数据。 案例:使用mysql客户端工具连接服务器端(用户名:root;密码:123456). [rootmysql-server ~]#…...
JavaFX:窗体显示状态,模态非模态
程序窗体显示一般有3中模式。非模态和模态,其中模态又分为程序模态和窗体模态。 非模态可以理解为窗体之间没有任何限制,可以用鼠标、键盘等工具在窗体间切换。 程序模态是窗体打开后,该程序的所有窗体都被冻结,无法切换&#x…...
C++17中std::filesystem::path的使用
C17引入了std::filesystem库(文件系统库, filesystem library)。这里整理下std::filesystem::path的使用。 std::filesystem::path,文件系统路径,提供了对文件系统及其组件(例如路径、常规文件和目录)执行操作的工具。此path类主要用法包括&#x…...
命令模式简介
概念: 命令模式是一种行为设计模式,它将请求封装成一个对象,从而允许您将不同的请求参数化、队列化,并且能够在不同的时间点执行。通过引入命令对象(Command)来解耦发送者(Invoker)…...
Boost序列化指针
Boost.Serialization 还能序列化指针和引用。 由于指针存储对象的地址,序列化对象的地址没有什么意义,而是在序列化指针和引用时,对象的引用被自动地序列化。 代码 #include <boost/archive/text_oarchive.hpp> #include <boost/…...
NIO简单介绍
一、什么是NIO 1、Java NIO全称java non-blocking IO, 是指JDK提供的新API。从JDK1.4开始,Java提供了一系列改进的输入/输出的新特性,被统称为NIO(即New IO),是同步非阻塞的 2、NIO有三大核心部分: Channel(通道), Buf…...
linux进程杀不死
项目场景: 虚拟机 问题描述 linux进程杀不死 无反应 原因分析: 进程僵死zombie 解决方案: 进proc或者find命令找到进程所在地址 cat status查看进程杀死子进程...
5分钟带你搞懂RPA到底是什么?RPA能做什么?
一、RPA的定义 RPA,全称Robotic Process Automation,即机器人流程自动化,是一种软件解决方案,能够模拟人类在计算机上执行的操作,以实现重复性、繁琐任务的自动化。它与传统的计算机自动化有所不同,因为它…...
毫米波雷达 TI IWR1443 在 ROS 中进行 octomap 建图
个人实验记录 /mmwave_ti_ros/ros_driver/src/ti_mmwave_rospkg/launch/1443_multi_3d_0.launch <launch><!-- Input arguments --><arg name"device" value"1443" doc"TI mmWave sensor device type [1443, 1642]"/><arg…...
113双周赛
题目列表 2855. 使数组成为递增数组的最少右移次数 2856. 删除数对后的最小数组长度 2857. 统计距离为 k 的点对 2858. 可以到达每一个节点的最少边反转次数 一、使数组成为递增数组的最少右移次数 这题可以直接暴力求解,枚举出每种右移后的数组,将…...
React 全栈体系(九)
第五章 React 路由 一、相关理解 1. SPA 的理解 单页 Web 应用(single page web application,SPA)。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面,只会做页面的局部更新。数据都需要通过 ajax 请求获取, 并在前端…...
阈值化分割,对灰度级图像进行二值化处理(数字图像处理大题复习 P8)
文章目录 画出表格求出灰度直方图 & 山谷画出结果图 画出表格 有几个0就写几,有几个1就写几,如图 求出灰度直方图 & 山谷 跟前面求灰度直方图的方法一样,找出谷底,发现结果为 4 画出结果图 最终的结果就是…...
vue3中withDefaults是什么
问: const props withDefaults(defineProps<{// 数据列表lotteryList: { pic: string; name?: string }[];// 中奖idwinId: number;// 抽奖初始转动速度initSpeed: number;// 抽奖最快转动速度fastSpeed: number;// 抽奖最慢转动速度slowSpeed: number;// 基本圈数baseCi…...
Android进阶之路 - 盈利、亏损金额格式化
在金融类型的app中,关于金额、数字都相对敏感和常见一些,在此仅记录我在金融行业期间学到的皮毛,如后续遇到新的场景也会加入该篇 该篇大多采用 Kotlin 扩展函数的方式进行记录,尽可能熟悉 Kotlin 基础知识 兄弟 Blog StringUti…...
工业蒸汽量预测(速通一)
工业蒸汽量预测(一) 赛题理解1、评估指标2、赛题模型3、解题思路 理论知识1、变量识别2、变量分析3、缺失值处理4、异常值处理5、变量转换6、新变量生成 数据探索1、导包2、读取数据3、查看数据4、可视化数据分布4.1箱型图4.2获取异常数据并画图4.3直方图…...
使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
UR 协作机器人「三剑客」:精密轻量担当(UR7e)、全能协作主力(UR12e)、重型任务专家(UR15)
UR协作机器人正以其卓越性能在现代制造业自动化中扮演重要角色。UR7e、UR12e和UR15通过创新技术和精准设计满足了不同行业的多样化需求。其中,UR15以其速度、精度及人工智能准备能力成为自动化领域的重要突破。UR7e和UR12e则在负载规格和市场定位上不断优化…...
智能仓储的未来:自动化、AI与数据分析如何重塑物流中心
当仓库学会“思考”,物流的终极形态正在诞生 想象这样的场景: 凌晨3点,某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径;AI视觉系统在0.1秒内扫描包裹信息;数字孪生平台正模拟次日峰值流量压力…...
select、poll、epoll 与 Reactor 模式
在高并发网络编程领域,高效处理大量连接和 I/O 事件是系统性能的关键。select、poll、epoll 作为 I/O 多路复用技术的代表,以及基于它们实现的 Reactor 模式,为开发者提供了强大的工具。本文将深入探讨这些技术的底层原理、优缺点。 一、I…...
C++ Visual Studio 2017厂商给的源码没有.sln文件 易兆微芯片下载工具加开机动画下载。
1.先用Visual Studio 2017打开Yichip YC31xx loader.vcxproj,再用Visual Studio 2022打开。再保侟就有.sln文件了。 易兆微芯片下载工具加开机动画下载 ExtraDownloadFile1Info.\logo.bin|0|0|10D2000|0 MFC应用兼容CMD 在BOOL CYichipYC31xxloaderDlg::OnIni…...
Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
