C++高级编程:构建高效稳定接口与深入对象设计技巧
C++高级编程:构建高效稳定接口与深入对象设计技巧
建立稳定接口
类是C++中的主要抽象单位。你应该将抽象原则应用于你的类,尽可能将接口与实现分离。具体来说,你应该使所有数据成员私有,并可选择性地提供getter和setter方法。这就是SpreadsheetCell类的实现方式:m_value是私有的,而公共的set()方法设置值,getValue()和getString()方法检索值。
使用接口和实现类
即便采取了上述措施和最佳设计原则,C++语言本质上对抽象原则不友好。其语法要求你将公共接口和私有(或受保护的)数据成员及方法组合在一个类定义中,从而将类的一些内部实现细节暴露给其客户端。这样做的缺点是,如果你需要在类中添加新的非公开方法或数据成员,所有使用该类的客户端都必须重新编译。这在大型项目中可能成为负担。
好消息是你可以让你的接口更加干净,并隐藏所有实现细节,从而实现稳定的接口。坏消息是这需要一些编码工作。基本原则是为你想编写的每个类定义两个类:接口类和实现类。实现类与你在不采取此方法时编写的类相同。接口类提供与实现类相同的公共方法,但它只有一个数据成员:指向实现类对象的指针。这被称为pimp习语,私有实现习语,或桥接模式。接口类的方法实现简单地调用实现类对象上的等效方法。
这样的结果是,无论实现如何改变,都不会影响公共接口类。这减少了重新编译的需要。如果实现(仅实现)发生变化,使用接口类的客户端无需重新编译。请注意,这种习语仅在单一数据成员是指向实现类的指针时才有效。如果它是按值数据成员,则在实现类定义发生变化时,客户端必须重新编译。
要在Spreadsheet类中使用此方法,请定义以下公共接口类,称为Spreadsheet。
module;
#include <cstddef>
export module spreadsheet;
export import spreadsheet_cell;
import <memory>;export class SpreadsheetApplication { };export class Spreadsheet {
public:Spreadsheet(const SpreadsheetApplication& theApp, size_t width = MaxWidth, size_t height = MaxHeight);Spreadsheet(const Spreadsheet& src);Spreadsheet(Spreadsheet&&) noexcept;~Spreadsheet();Spreadsheet& operator=(const Spreadsheet& rhs);Spreadsheet& operator=(Spreadsheet&&) noexcept;void setCellAt(size_t x, size_t y, const SpreadsheetCell& cell);SpreadsheetCell& getCellAt(size_t x, size_t y);size_t getId() const;static const size_t MaxHeight { 100 };static const size_t MaxWidth { 100 };void swap(Spreadsheet& other) noexcept;private:class Impl;std::unique_ptr<Impl> m_impl;
};export void swap(Spreadsheet& first, Spreadsheet& second) noexcept;
实现类Impl是一个私有嵌套类,因为除了Spreadsheet类之外,没有人需要了解这个实现类。现在,Spreadsheet类只包含一个数据成员:指向Impl实例的指针。公共方法与旧的Spreadsheet类相同。
掌握类和对象
嵌套的Spreadsheet::Impl类在spreadsheet模块的实现文件中定义。它应该对客户端隐藏,因此不导出Impl类。Spreadsheet.cpp模块实现文件如下开始:
module;
#include <cstddef>
module spreadsheet;
import <utility>;
import <stdexcept>;
import <format>;
import <algorithm>;
using namespace std;// Spreadsheet::Impl类定义。
class Spreadsheet::Impl {/* 为简洁起见省略 */
};// Spreadsheet::Impl方法定义。
Spreadsheet::Impl::Impl(const SpreadsheetApplication& theApp, size_t width, size_t height)
: m_id { ms_counter++ }
, m_width { min(width, Spreadsheet::MaxWidth) }
, m_height { min(height, Spreadsheet::MaxHeight) }
, m_theApp { theApp }
{m_cells = new SpreadsheetCell*[m_width];for (size_t i{ 0 }; i < m_width; i++) {m_cells[i] = new SpreadsheetCell[m_height];}
}
// 其他方法定义省略以简洁。
Impl类几乎具有与原始Spreadsheet类相同的接口。对于方法实现,需要记住Impl是一个嵌套类;因此,你需要指定作用域为Spreadsheet::Impl。所以,对于构造函数,它变成了Spreadsheet::Impl::Impl(…)。
由于Spreadsheet类具有指向实现类的unique_ptr,因此Spreadsheet类需要有用户声明的析构函数。由于我们不需要在此析构函数中执行任何操作,因此可以在实现文件中将其默认为:
Spreadsheet::~Spreadsheet() = default;
事实上,它必须在实现文件中默认,而不是直接在类定义中。原因是Impl类仅在Spreadsheet类定义中前向声明;也就是说,编译器知道将会有一个Spreadsheet::Impl类出现在某处,但此时它还不知道定义。因此,你不能在类定义中默认析构函数,因为编译器会尝试使用尚未定义的Impl类的析构函数。在这种情况下,对其他方法进行默认操作时也是如此,例如移动构造函数和移动赋值运算符。
实现Spreadsheet方法
Spreadsheet类的方法实现,如setCellAt()和getCellAt(),只是将请求传递给底层的Impl对象:
void Spreadsheet::setCellAt(size_t x, size_t y, const SpreadsheetCell& cell) {m_impl->setCellAt(x, y, cell);
}SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y) {return m_impl->getCellAt(x, y);
}
Spreadsheet的构造函数必须构造一个新的Impl以执行其工作:
Spreadsheet::Spreadsheet(const SpreadsheetApplication& theApp, size_t width, size_t height) {m_impl = make_unique<Impl>(theApp, width, height);
}Spreadsheet::Spreadsheet(const Spreadsheet& src) {m_impl = make_unique<Impl>(*src.m_impl);
}
拷贝构造函数看起来有些奇怪,因为它需要从源Spreadsheet复制底层的Impl。拷贝构造函数接受一个Impl的引用,而不是指针,所以你必须解引用m_impl指针来获取对象本身。
Spreadsheet赋值运算符必须同样将赋值传递给底层的Impl:
Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs) {*m_impl = *rhs.m_impl;return *this;
}
赋值运算符中的第一行看起来有些奇怪。Spreadsheet赋值运算符需要将调用转发给Impl赋值运算符,这只在你复制直接对象时运行。通过解引用m_impl指针,你强制执行直接对象赋值,这导致调用Impl的赋值运算符。
swap()方法简单地交换单一数据成员:
void Spreadsheet::swap(Spreadsheet& other) noexcept {std::swap(m_impl, other.m_impl);
}
这种技术将接口与实现真正分离,是非常强大的。虽然一开始有些笨拙,但一旦习惯了,你会发现它很自然易用。然而,在大多数工作环境中,这不是常见做法,所以你可能会遇到同事的一些抵触。支持这种做法的最有力论据不是分离接口的美学,而是如果类的实现发生变化,构建时间的加速。
注意
使用稳定的接口类,可以减少构建时间。将实现与接口分离的另一种方法是使用抽象接口,即只有纯虚方法的接口,然后有一个实现该接口的实现类。这是下个主题。
相关文章:
C++高级编程:构建高效稳定接口与深入对象设计技巧
C高级编程:构建高效稳定接口与深入对象设计技巧 建立稳定接口 类是C中的主要抽象单位。你应该将抽象原则应用于你的类,尽可能将接口与实现分离。具体来说,你应该使所有数据成员私有,并可选择性地提供getter和setter方法。这就是…...
Qt——连接mysql增删查改(仓库管理极简版)
目录 UI布局设计 .pro文件 mainwindow.h main.cpp UI布局设计 .pro文件 QT core gui QT core gui sql QT sqlgreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler emit warnings if you use # any …...
Panda3d 场景管理
Panda3d 场景管理 文章目录 Panda3d 场景管理有关分层场景图的重要信息NodePathNodePath 以及 Node 的函数调用模型文件文件格式加载模型文件将模型放置在场景图中模型缓存压缩模型异步加载模型通过回调函数进行 常见的状态变化修改节点的位置和姿态改变父级节点改变颜色隐藏和…...
京东数据分析(京东销量):2023年9月京东投影机行业品牌销售排行榜
鲸参谋监测的京东平台9月份投影机市场销售数据已出炉! 根据鲸参谋电商数据分析平台的相关数据数据显示,9月份,京东平台投影机的销量为13万,环比下滑约17%,同比下滑约25%;销售额将近2.6亿,环比下…...
uniapp cli化一键游项目启动报错总结
问题1、使用hbuilder运行指令,开始编译后没有反应,使用命令构建自行结束进程 解决:因为使用了node16.24,卸载重新安装14.17后解决 问题2、 21:31:11.483 Module build failed (from ./node_modules/vue/cli-service/node_module…...
我的月光宝盒初体验失败了
哈哈哈,我爱docker, docker 使我自由!!! docker make me free! 菠萝菠萝蜜口号喊起来。 https://github.com/vivo/MoonBox/ windows上安装好了docker之后,docker-compose是自带的。 docker-compose -f docker-compo…...
vue3+vite搭建后台项目-1 引入element-plus 中文包,打包时报错问题
vue3vite搭建后台项目-1 引入element-plus 中文包,打包时报错问题 终端报错 If theelement-pluspackage actually exposes this module, try adding a new declaration (.d.ts) file containing are moduleelement-plus/dist/locale/zh-cn.mjsdec import zhCn fromelement-plus…...
带你详细了解git的【分支和标签】
🏅我是默,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《git》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,…...
分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测
分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测 目录 分类预测 | Matlab实现PSO-LSTM粒子群算法优化长短期记忆神经网络的数据多输入分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现PSO-LSTM粒子群算法优化长短…...
Spring 事务失效的场景
1.直接new出来的对象添加事务不起作用,因为只有spring定义的bean才接受事务。 2.由于mysql的引擎用Myisam不支持事务,所以如果使用mysql的myisam引擎的话,事务不起作用。 3.如果Transaction注解到非public方法上,事务不起作用&…...
酷柚易汛ERP-自定义打印整体介绍
1、产品介绍 每种单据系统预设常用模板,提供A4纸张、三等分、二等分,销货单额外提供80mm、58mm供用户选择;每张单据可设置一个默认模板和多个常用模;除默认模板外,其他模板都允许删除,用户可以根据公司业务…...
activiti命令模式与责任链模式
来源:activiti学习(七)——命令模式和职责链模式在activiti中的应用 文章目录 设计模式命令模式CommandHelloCommandByeCommand ReceiverInvokerClient 职责链模式AbstractHandlerConcreteHandlerAConcreateHandlerB Client activiti中很多ap…...
C++20 Text formatting
C20 Text formatting 格式化字符串, 和 python 类似。 std::formatter - cppreference.com string — Common string operations — Python 3.12.0 documentation 新格式库位于 <format> 头文件中。格式库基于 Python3 中的 str.format() 方法建模。格式…...
redis-plus-plus--github中文翻译--2
12 能不能举个例子 当然可以。以下是一个具体的例子,说明如何使用cmake命令为redis-plus-plus配置编译和安装路径: 假设: hiredis 被安装在 /opt/libs/hiredis你想要将 redis-plus-plus 安装到 /opt/libs/redis-plus-plus那么,你可以使用以下的 cmake 命令: cmake -DCM…...
Vuex状态管理:Getters :VOA模式
简介: Getters 用于对 Store 中的数据进行加工处理形成新的数据。 Getters 可以对 Store 中已有的数据加工处理之后形成新的数据,类似 Vue 的计算属性。 Store 中数据发生变化,Getters 的数据也会跟着变化。 案列 /src/store/index.js状态…...
二十三种设计模式全面解析-享元模式(Flyweight Pattern)详解:构建高效共享的对象结构
在软件开发中,我们经常会面临大量相似对象的创建和管理问题。这些相似对象的创建和销毁过程可能会占用大量的内存和系统资源,导致性能下降。为了解决这个问题,享元模式(Flyweight Pattern)应运而生。本文将深入探讨享元…...
华为ensp:交换机接口划分vlan
现在要把 e0/0/1 接口放入vlan1 e0/0/2 接口放入vlan2 e0/0/3 接口放入vlan3 默认所有接口都在vlan1所以 e0/0/0 接口不用动 1.创建vlan 进入系统视图模式 直接输入 vlan 编号 即可创建对应vlan vlan 编号 vlan 2 创建vlan2 vlan 3 创建vlan3 2.将接口进入vlan…...
PCBA表面污染的分类及处理方法
NO.1 引言 在PCBA生产过程中,锡膏和助焊剂会产生残留物质,残留物中包含的有机酸和电离子,前者易腐蚀PCBA,后者会造成焊盘间短路故障。且近年来,用户对产品的清洁度要求越来越严格,PCBA清洗工艺逐渐被电子组…...
Linux开发工具之编辑器vim
文章目录 1.vim是啥?1.1问问度娘1.2自己总结 2.vim的初步了解2.1进入和退出2.2vim的模式1.介绍2.使用 3.vim的配置3.1自己配置3.2下载插件3.3安装大佬配置好的文件 4.程序的翻译 1.vim是啥? 1.1问问度娘 1.2自己总结 vi/vim都是多模式编辑器,vim是vi的升级版本&a…...
【Hadoop实战】Hadoop指标系统V2分析
Hadoop指标系统V2分析 文章目录 Hadoop指标系统V2分析架构主要组成部分根据图表解释数据流向指标过滤JMX的应用开启指标系统的组件指标项说明 使用HTTP(JMXJsonServlet)获取指标接口调用方式GET查询的逻辑数据的来源,以及更新的原理 架构 在…...
微信小程序之bind和catch
这两个呢,都是绑定事件用的,具体使用有些小区别。 官方文档: 事件冒泡处理不同 bind:绑定的事件会向上冒泡,即触发当前组件的事件后,还会继续触发父组件的相同事件。例如,有一个子视图绑定了b…...
【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
解决Ubuntu22.04 VMware失败的问题 ubuntu入门之二十八
现象1 打开VMware失败 Ubuntu升级之后打开VMware上报需要安装vmmon和vmnet,点击确认后如下提示 最终上报fail 解决方法 内核升级导致,需要在新内核下重新下载编译安装 查看版本 $ vmware -v VMware Workstation 17.5.1 build-23298084$ lsb_release…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
五年级数学知识边界总结思考-下册
目录 一、背景二、过程1.观察物体小学五年级下册“观察物体”知识点详解:由来、作用与意义**一、知识点核心内容****二、知识点的由来:从生活实践到数学抽象****三、知识的作用:解决实际问题的工具****四、学习的意义:培养核心素养…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
