C++类设计和实现的十大最佳实践

news2025/1/11 16:49:34

C++代码提供了足够的灵活性,因此对于大部分工程师来说都很难把握。本文介绍了写好C++代码需要遵循的10个最佳实践,并在最后提供了一个工具可以帮助我们分析C++代码的健壮度。原文:10 Best practices to design and implement a C++ class

1. 尽可能尝试使用新的C++标准

到2022年,C++已经走过了40多个年头。新的C++标准实际上简化了许多令人沮丧的细节,提供了新的现代方法来改进C++代码,但让开发人员认识到这一点并不容易。

以内存管理为例,这可能是C++中受到最多批评的机制。多年来,对象分配都是由new关键字完成的,开发人员一定得记住在代码的某个地方调用delete。“现代C++”解决了这个问题,并促进了共享指针的使用。

2. 使用命名空间模块化代码

现代C++库广泛使用命名空间来模块化代码库,它们利用“Namespace-by-feature”方法,按功能划分命名空间来反映功能集,将单个特性(且仅与该特性)相关的所有内容放到单个命名空间中。从而使得命名空间具有高内聚性和高模块化,并且耦合最小,紧耦合的项目被放在了一起。

Boost是按特性分组的最佳示例,其包含数千个命名空间,每个命名空间用于对特定的特性进行分组。

3. 抽象

数据抽象是C++中面向对象编程最基本和最重要的特性之一。抽象意味着只显示基本信息而隐藏细节,数据抽象指的是仅向外部世界提供关于数据的基本信息,隐藏背景细节或实现。

尽管许多书籍、网络资源、会议演讲者和专家都推荐这种最佳实践,但在很多项目中,这条规则仍然被忽略了,许多类的细节并没有被隐藏。

4. 类越小越好

具有多行代码的类型应该被划分为一组较小的类型。

需要很大的耐心重构一个大的类,甚至可能需要从头重新创建所有东西。以下是一些重构建议:

  • BigClass中的逻辑必须被分成更小的类。这些较小的类最终可能成为嵌套在原始God Class中的私有类,God Class的实例对象由较小嵌套类的实例组成。

  • 较小的类划分应该由God Class负责的多个职责驱动。要确定这些职责,通常需要查找与字段的子集强耦合的方法的子集。

  • 如果BigClass包含的逻辑比状态多,一个好的选择是定义一个或几个不包含静态字段而只包含纯静态方法的静态类。纯静态方法是一种只根据输入参数计算结果的函数,它不读取或分配任何静态或实例字段。纯静态方法的主要优点是易于测试。

  • 首先尝试维护BigClass的接口,并委托调用新提取的类。最后,BigClass应该是一个没有自己逻辑的纯接口,可以为了方便将其保留,也可以将其扔掉,并开始只使用新类。

  • 单元测试可以提供帮助: 在提取方法之前为每个方法编写测试,以确保不会破坏功能。

5. 每个类尽量提供最少的方法

包含20个以上方法的类可能很难理解和维护。

一个类有许多方法可能是实现了太多责任的症状。

也许所面对的类控制了系统中太多的其他类,并且已经超出了应有的逻辑,成为了一个无所不能的类。

6. 加强低耦合

低耦合是理想状态,可以在应用中进行较少的更改实现程序的某个变更。从长远来看,可以减少修改、添加新特性的大量时间、精力和成本。

低耦合可以通过使用抽象类或泛型类和方法来实现。

7. 加强高内聚

单一责任原则规定一个类不应该有多于一个更改的理由,这样的类被称为内聚类。较高的LCOM值通常可以意味着类的内聚性较差。有几个LCOM指标,取值范围为[0-1]。LCOM HS (HS代表Henderson-Sellers)取值范围为[0-2]。LCOM HS值大于1时需要产生警惕。下面是计算LCOM指标:

LCOM = 1 — (sum(MF)/M*F)
LCOM HS = (M — sum(MF)/F)(M-1)

其中……

  • M是类中方法的数量(包括静态方法和实例方法,它还包括构造函数、属性getter/setter、事件添加/删除方法)。

  • F是类中实例字段的数量。

  • MF是类访问特定实例字段的方法数量。

  • Sum(MF)是该类所有实例字段的MF之和。

这些公式背后的基本思想可以表述如下: 如果一个类的所有方法都使用它的所有实例字段,那么这个类就是完全内聚的,这意味着sum(MF)=M*F,然后LCOM = 0和LCOMHS = 0。

LCOMHS值大于1就需要警惕了。

8. 只注释代码不能表达的内容

鹦鹉学舌的代码注释没有为读者提供任何额外的东西。代码库中充斥着嘈杂的注释和不正确的注释,促使程序员忽略所有的注释,或者采取积极的措施隐藏它们。

9. 尽量不要用重复的代码

众所周知,重复代码的存在对软件开发和维护有负面影响。实际上,一个主要缺点是,当为了修复bug或添加新特性而更改重复代码的实例时,所有对应的代码必须同时更改。

产生重复代码最常见的原因是复制/粘贴操作,这种情况下,相似的源代码出现在两个或多个地方。许多文章、书籍和网站都警告不要采用这种做法,但有时实践这些建议并不容易,开发人员还是会选择简单的解决方案: 复制/粘贴大法。

使用适当的工具可以容易的从复制/粘贴操作中检测到重复代码,但是,在某些情况下,克隆代码很难被检测到。

10. 不变性有助于多线程编程

基本上,如果对象在创建之后状态不变,那么这个对象就是不可变(immutable)的。如果一个类的实例是不可变的,那么该类就是不可变的。

不可变对象极大简化了并发编程,这是支持使用它的重要理由。想想看,为什么编写适当的多线程程序是一项艰巨的任务?因为同步线程访问资源(对象或其他操作系统资源)是很困难的。为什么同步这些访问很困难?因为很难保证多个线程对多个对象进行的多次写访问和读访问之间不会出现竞争条件。如果不再有写访问会怎么样?换句话说,如果被线程访问的对象的状态没有改变会怎么样?就不再需要同步了!

关于不可变类的另一个好处是它们永远不会违反里氏替换原则(LSP, Liskov Subtitution Principle),以下是百科对LSP的定义:

Liskov的行为子类型的概念定义了可变对象可替换性的概念,也就是说,如果S是T的子类型,那么程序中T类型的对象可以被替换为S类型的对象,而不改变该程序的任何期望属性(例如,正确性)。

如果没有公共字段,没有可以更改其内部数据的方法,并且派生类方法无法更改其内部数据,那么引用对象类就是不可变的。因为值不可变,所以在所有情况下都可以引用相同的对象,不需要复制构造函数或赋值操作符。出于这个原因,建议将复制构造函数和赋值操作符设为私有,或者从boost::noncopyable继承,或者使用新的C++ 11特性“显式默认和删除特殊成员函数”[2]。

如何加强对这些最佳实践进行检查?

CppDepend[3]提供了名为CQLinq[4]的代码查询语言,可以像数据库一样查询代码库。开发人员、设计人员和架构师可以自定义查询,以便轻松找到容易出现bug的情况。

通过CQLinq,可以结合来自代码度量、依赖关系、API使用和其他模型的数据来定义非常高级的查询,以匹配容易出现bug的情况。

例如,分析clang源代码后,可以检测到大类:

检测到有大量方法的类:

或者检测到内聚性较差的类:

References:
[1] 10 Best practices to design and implement a C++ class: https://issamvb.medium.com/10-best-practices-to-design-and-implement-a-c-class-4326611827e1#:~:text=10%20Best%20practices%20to%20design%20and%20implement%20a,class%20as%20you%20can.%20...%20More%20items...%20
[2] Explicitly defaulted and deleted special member functions: http://en.wikipedia.org/wiki/C%2B%2B11#Explicitly_defaulted_and_deleted_special_member_functions
[3] CppDepend: http://www.cppdepend.com/
[4] CQLinq: https://www.cppdepend.com/cqlinq

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

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

相关文章

Word处理控件Aspose.Words功能演示:使用 C# 将 Word 文档转换为 HTML

在各种情况下,您需要在 Web 或桌面应用程序中显示 Word 文档的内容。在这种情况下,合适的选项之一是将 Word 文档转换为HTML。为了在 .NET 应用程序中实现这一点,本文介绍了如何使用 C# 将 Word DOCX或DOC文件转换为 HTML。此外,您…

搅拌釜反应器全自动真空压力(正负压)控制解决方案

摘要:针对双层玻璃反应釜中存在的无法进行真空压力自动和准确控制等问题,本文提出了完整和成熟的解决方案,即采用卫生级电动调节阀和高精度双通道PID控制器,结合不同量程的真空计,与反应器、真空泵和正压气源构成闭环控…

ARP协议与ARP欺骗

一、ARP协议(地址解析协议) 所谓“地址解析”就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程,ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。 将一个已知的IP地址解析为MAC…

169. 基于Django-RESTFramework的节流的使用

1.节流概述 节流又称限流,限制访问。 就是一个用户多次发送一个请求(页面或者链接)的时候,单位时间允许访问次数限制,超过限制就会出现访问受限,提示譬如:离下一场访问还有多久之类等的字样。 …

基于SSM的学生考勤管理系统的设计与实现

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问…

【学习笔记】顺序容器的表格方式总结 C++

目录顺序容器及其特点顺序容器操作向顺序容器添加元素insertemplace参考更新中… 顺序容器及其特点 名字访问元素插入,删除元素vector(可变大小数组)支持快速随机访问在尾部之外的位置插入或删除元素可能很慢deque(双端队列&…

SpringBoot实战:整合MapStruct实现数据类型转化

MapStruct 是一个代码生成器,它基于约定优于配置方法极大地简化了 Java bean 类型之间映射的实现。自动生成的映射转换代码只使用简单的方法调用,因此速度快、类型安全而且易于理解阅读;本篇就是实现 SpringBoot 整合 MapStruct 实现数据类型…

[附源码]JAVA毕业设计小区物业管理系统演示录像2020(系统+LW)

[附源码]JAVA毕业设计小区物业管理系统演示录像2020(系统LW) 项目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09…

ffmpeg库安装及入门指南(Windows篇)- 2022年底钜献

最近项目需要,使用了 ffmpeg 做摄像头视频采集和串流。这几天有点时间,打算把相关的一些知识记录分享一下。 在撰写本文时,我又在另外一台电脑上把 ffmpeg 重新安装了一遍,所以绝对真实靠谱!如果你觉得文章写得还不错…

代码效果测试

ROBUST TEXT DETECTION IN NATURAL IMAGES WITH EDGE ENHANCED MAXIMALLY STABLE EXTREMAL REGIONS 代码地址:https://github.com/akab/TextDetection 基本步骤: 1)将原图转为灰度图; 2)使用OpenCV的canny函数进行边…

HTML -- 常用标签及示例总结

文章目录HTML常用标签1 标签语义1.1 标签属性2 标题标签 h1 - h63 段落和换行标签3.1 段落标签3.2 换行标签4 文本格式化标签5 div和span6 图像标签7 超链接标签7.1 链接的语法格式7.2 链接分类8 表格标签8.1 表格的主要作用8.2 表格的基本语法8.3 表头单元格标签8.4 表格属性8…

C语言随机数的产生(rand、srand、time函数细节讲解)

✨C语言随机数的产生随机数生成代码&#xff1a;rand函数&#xff1a;srand函数&#xff1a;time函数&#xff1a;time函数补充:time_t类型讨论time_t指针传入的讨论&#xff1a;随机数生成代码&#xff1a; 先上代码&#xff0c;再详细解释&#xff1a; #include<stdio.h…

Python数组数据处理办法清单

import numpy as np import pandas as pd import matplotlib.pyplot as plt#matplotlib 的字体&#xff08;font&#xff09;为黑体&#xff08;SimHei&#xff09; plt.rcParams[font.sans-serif] [SimHei, ] # matplotlib正确显示正负号 plt.rcParams[axes.unicode_minus] …

dfs序(树形结构线性化)

dfs序是将树形结构转换为线性结构的一种方式。 dfs序 dfs序&#xff1a; 指每个节点在dfs深度优先遍历中的进出栈的时间序列。 定义三个数组&#xff1a; in[x]&#xff1a;表示结点x 入栈的时间戳。 out[x]&#xff1a;表示结点x出栈的时间戳&#xff0c;特殊的&#xff0c;出…

Linux网络原理及编程(5)——第十五节 TCP的连接(三次握手、四次挥手)

目录 三次握手 四次挥手 我们来重点说说两个状态&#xff1a;CLOSE_WAIT和TIME_WAIT 【CLOSE_WAIT】 【TIME_WAIT】 各位好&#xff0c;博主新建了个公众号《自学编程村》&#xff0c;拉到底部即可看到&#xff0c;有情趣可以关注看看哈哈&#xff0c;关注后还可以加博主w…

Linux CENTOS安装mysql8 64位

1. 查看系统中是否已经安装了mariadb 执行&#xff1a;yum list installed | grep mariadb 若已经存在&#xff0c;则删除 执行&#xff1a; yum remove mariadb 2.下载需要安装的压缩文件&#xff0c;从官网下载&#xff0c;文件为名字为 mysql-8.0.31-linux-glibc2.12-x86_6…

S7-1200和1500PLC与条码枪建立TCP_IP通信的具体方法示例

S7-1200和1500PLC与条码枪建立TCP_IP通信的具体方法示例 今天和大家分享西门子S7-1200和1500PLC如何获取条码枪的扫描数据,这种案例中一般PLC作为TCP/IP的客户端(只接收数据,不发送数据),条码枪作为TCP/IP的服务器。 如下图所示,在指令—通信—开放式用户通信中可以找到相关…

洞察 | 软件定义汽车时代下,汽车制造产业的应对策略

汽车产业作为先进制造行业&#xff0c;伴随着数字化技术&#xff0c;电气化技术的迭代正快速的成长&#xff0c;而随着智能汽车、自动驾驶概念的提出&#xff0c;整车制造的软硬件复杂度也在持续提升&#xff0c;软件定义汽车转型已成整体汽车制造产业的共识。 据中国软件行业…

成长任务| 挑战代码画颗圣诞树

&#x1f384;立即投稿&#x1f384; &#x1f4cc;活动规则 圣诞将至&#xff0c;这次来试试用代码过圣诞吧~挑战圣诞树的一万种画法! √ 使用代码画一颗#圣诞树#&#xff0c;将你的代码文件通过本页面投稿&#xff0c;审核通过即可参与活动 √ 可使用任意编程语言&#xff…

【解纠缠表示:图像增强】

DRLIE: Flexible Low-Light Image Enhancement via Disentangled Representations &#xff08;DRLIE&#xff1a;基于解纠缠表示的柔性弱光图像增强&#xff09; 解纠缠表示 弱光图像增强&#xff08;Low-light image enhancement (LIME)&#xff09;是将亮度不理想的图像转化…