Effective C++ 改善程序与设计的55个具体做法笔记与心得 4
四. 设计与声明
18. 让接口容易被正确使用,不易被误用
请记住:
- 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质
- “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
- “阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任。
- trl::shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等等。
解释:
设计优秀的接口确实要注重以下几个方面:
-
易于正确使用:一个良好设计的接口应该使用户能够更容易地使用它。这需要避免在接口设计中的歧义和不一致,并尽量与已有的、用户熟悉的模式保持一致。例如,对一致性和对内置类型行为的兼容都属于这种情况。
-
不容易被误用:我们也应确保接口能够防止用户误用。创建新的类型(以区分不同的概念或值),限制类型上的操作,约束对象的值,或者管理客户端的资源,都是有效的防止误用手段。
std::shared_ptr
的删除器就是一个很好的例子。这个特性让我们能够自定义对象被删除时的行为。例如,当 std::shared_ptr
管理的资源是一个在动态链接库(DLL)中分配的对象,或者是一个需要在释放之前执行特定操作(例如解锁)的资源时,删除器就非常有用了。
以下是一个 std::shared_ptr
如何使用自定义删除器的例子:
// 假设 lock 是一个互斥锁对象
std::shared_ptr<std::mutex> lock(new std::mutex, [](std::mutex* m){m->unlock(); // 解锁delete m;
});
这里,我们创建了一个 std::shared_ptr
来管理一个 std::mutex
对象,并提供了一个自定义的删除器。当 std::shared_ptr
需要释放它管理的对象时,它就会先调用 unlock
方法,然后再删除对象。这样,我们就不需要关心何时解锁或删除互斥锁对象, std::shared_ptr
会帮我们自动完成。
19. 设计class犹如设计type
请记住:
- 新type的对象应该如何被创建和销毁?
- 对象的初始化和对象的赋值该有什么样的差别?
- 新type的对象如果被passed by value(以值传递),意味着什么?
- 什么是新type的“合法值”?
- 你的新type需要配合某个继承图系吗?
- 你的新type需要什么样的转换?
- 什么样的操作符和函数对此新type而言是合理的?
- 什么样的标准函数应该驳回?
- 该取用新type的成员?
- 什么是新type的“未声明接口”?
- 你的新type有多么一般化?你真的需要一个新type么?
解释:
-
新type的对象应该如何被创建和销毁?:这一问题涉及到类的构造函数和析构函数的设计。构造函数决定如何初始化一个对象,析构函数决定如何清理它。
-
对象的初始化和对象的赋值该有什么样的差别?: 初始化涉及创建新对象时赋予其初始值,而赋值则是将已存在对象的值改变为新的值。这两者的处理可能会有所不同,因此我们通常需要对这两种操作进行清晰的定义。
-
新type的对象如果被passed by value(以值传递),意味着什么?:如果类对象被以值传递,那么会创建该对象的一个复制品。为此,我们需要定义复制构造器以指定如何进行复制。
-
什么是新type的“合法值”?:对于某个特定的类,其对象的“合法值”可能会受到某些约束。“合法值”的概念涉及到类的数据验证和封装。
-
你的新type需要配合某个继承图系吗?:如果新的类型是某个已有类型的特化,或者需要被其他类型进行扩展,那么你需要考虑使用继承。
-
你的新type需要什么样的转换?:在某些情况下,你可能需要为类定义转换运算符,例如转换为其他类类型或基本数据类型。
-
什么样的操作符和函数对此新type而言是合理的?:你需要定义对象可以进行哪些操作,这通常通过重载运算符和定义成员函数来实现。
-
什么样的标准函数应该驳回?:有些情况下,你可能想禁止某些操作,如禁止复制或者赋值等,可以通过将这些函数设为私有并不提供实现来达到这个目的。
-
该取用新type的成员?:注意保持类的封装性,尽可能通过公有成员函数获取和设定私有成员变量,而不是将成员变量设置为公有。
-
什么是新type的“未声明接口”?:这个可能指的是那些未直接列出但通过类的公有接口可以的操作。这种操作需要被考虑在内和进行测试。
-
你的新type有多么一般化?你真的需要一个新type么?:在你决定创建新的类时,需要考虑它是否过于特定或者过于一般化,以及是否真的需要一个新的类。如果对于问题的解决并没有太大帮助,可能需要重新考虑设计。
20. 宁以pass-by-reference-to-const替换pass-by-value
请记住:
- 尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高级,并可避免切割问题。
- 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对他们而言,pass-by-value往往比较适当。
解释:
-
尽量以pass-by-reference-to-const替换pass-by-value: 对象在传递过程中,pass-by-value需要对对象进行复制操作,产生新的对象。这通常会发生在函数参数传递和返回值中。但复制大型对象可能会非常耗时,也可能引发性能问题。为了避免这些问题,一种常见的解决方法是使用"传递常量引用",即pass-by-reference-to-const,这样就可以避免复制操作。同时使用const可以避免在函数内部修改原对象。
-
以上规则并不适用于内置类型,以及STL的迭代器和函数对象: 对于内置类型(如int、char等),以及STL的迭代器和函数对象,他们通常在内存占用和复制成本上非常小,因此使用pass-by-value通常会更高效。此外,这些类型通常设计为值语义,使用pass-by-value可以更符合其设计原则。
21. 必须返回对象时,别妄想返回其reference
请记住:
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。
解释:
-
绝不要返回pointer或reference指向一个local stack对象:这是因为当函数执行完毕后,它的栈内存会被销毁,那些局部变量也就不复存在了。如果你返回一个指向局部变量的指针或引用,那么该指针或引用就会变成悬挂指针或者悬挂引用,这种无效的引用可能会导致程序错误。
-
或返回reference指向一个heap-allocated对象:这主要是因为在返回引用到堆上分配的对象时,对象的生命周期控制可能变得复杂。如果在函数中分配了堆内存,但是没有正确返回该内存的指针,那么调用者可能根本不知道应该释放这个内存,这就导致了内存泄漏。
-
或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象:如果你返回一个指向局部静态变量的指针或引用,而在不同的上下文中需要多个这样的对象,那么这些上下文会共享该对象,这可能会导致出现意料之外的副作用。
总的来说,编程时很重要的一点就是管理好对象的生命周期,不正确的内存管理,如上述的几种情况,可能会引发很多问题。因此在编程时,我们要尽量避免这些错误的用法。
22. 将成员变量声明为private
请记住:
- 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性。
- protected并不比public更具封装性。
解释:
在面向对象编程中,封装是非常重要的特性之一。
-
将成员变量声明为private:这样做能保护类的内部状态,防止客户端代码直接修改它。我们只能通过定义的public方法来访问和修改,这样可以确保数据的安全性和一致性。
-
可细微划分访问控制:private限定符可以使类的成员只能被该类的方法访问,这样我们可以更精细地控制谁可以访问和修改类的状态。
-
约束条件获得保证:通过private属性和公开的setter方法,我们可以在修改数据前执行检查,保证数据满足一定的约束条件。
-
提供class作者以充分的实现弹性:因为客户端代码不能直接访问私有成员,所以我们在未来需要修改类的内部实现时会更加灵活,不需要担心会影响到已有的客户端代码。
-
protected并不比public更具封装性:protected成员可以被自身和任何子类访问,相比private,其访问权限更宽松,所以有时可能不如private符合封装性的理念。
所以,将数据成员设置为private并通过public方法进行访问和修改,是实现良好封装的常用手段。
23. 宁以non-member、non-friend替换member函数
请记住:
宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性。
解释:
-
增加封装性:在一些情况下,使用non-member non-friend函数(非成员非友元函数)可以增加类的封装性。这是因为non-member non-friend函数无法访问类的私有和受保护成员,所以对类的内部结构知之甚少。这就使得类的实现可以在不破坏这些函数正确性的情况下自由改变。
-
提高包裹弹性:如果我们知道函数不需要访问对象的私有或受保护成员,那么就没有必要将它作为类的成员函数,这就提供了更多的弹性。我们可以在不改变类定义的情况下添加更多的函数,或者将这些函数放入不同的命名空间中。
-
提升机能扩充性:non-member non-friend函数可以对多个对象执行操作,即使这些对象来自不同的类。相比之下,成员函数只能对它所属的对象执行操作。所以使用非成员非友元函数更加灵活,能更好地扩展功能。
24. 若所有参数皆需类型转换,请为此采用non-member函数
请记住:
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。
解释:
一个成员函数的隐式this
参数只能用来转换它所属的对象,而不能用来转换传给该函数的其他参数。
但是,非成员函数没有这样的限制,它们可以自由地转换传递给它们的所有参数。因此,如果一个操作需要对所有参数进行类型转换(包括那个由this
指针隐含的参数),那么这个操作通常应该由非成员函数来完成。
请注意,根据C++的运算符重载规则,有两个参数的运算符(例如+或-)应该作为非成员函数来实现,以便能处理左操作数进行的类型转换。然而,有些运算符(例如=或+=)则常常作为成员函数,因为它们通常需要改变它们的左操作数,即this
对象。这是由于它们通常需要直接访问对象的内部状态,而这正是成员函数所提供的。
25. 考虑写一个不抛异常的swap函数
请记住:
- 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。
- 如果你提供一个member swap,也该提供一个non-member swap用来调用前者。对于class(而非templates),也请特化std::swap。
- 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。
- 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。
解释:
-
提供一个swap成员函数:这样可以确保swap操作针对你的类型最为高效。确保这个函数不会抛出异常,这样可以使之在异常敏感的上下文环境中更安全。
-
提供一个non-member swap:非成员swap函数往往更易用,因为它们可以被引入到不需要访问类内部数据的函数或者范畴中。这个非成员函数应该简单地调用上面定义的成员swap函数。
-
特化std::swap:如果你的类型不适用于标准库提供的
std::swap
,你可以为你的类型提供一个std::swap
的全特化版本,这样可以使标准算法和容器能够利用你的高效swap实现。 -
不带任何“命名空间资格修饰”调用swap:这样可以确保在swap操作符重载的上下文中你总是调用了正确的swap版本。
-
不要在std内添加新东西:这是一个关于C++编程习惯的通常建议。尽管为
std::swap
提供全特化版本是可以接受的,但在std
命名空间内添加全新的内容是不被允许的,因为这可能引发未定义的行为。
相关文章:
Effective C++ 改善程序与设计的55个具体做法笔记与心得 4
四. 设计与声明 18. 让接口容易被正确使用,不易被误用 请记住: 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质“促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。“阻止误…...

WordPress管理员后台登录地址修改教程,WordPress admin登录地址文件修改方法
我们使用WordPress时,管理员后台登录默认地址为“域名/wp-login.php”或“域名/wp-admin”,为了安全,一般会把此地址改掉,防止有人恶意来攻击咱的WordPress,今天出个WordPress后台登录地址修改教程,修改之后…...

Python基础教程(二十四):日期和时间
💝💝💝首先,欢迎各位来到我的博客,很高兴能够在这里和您见面!希望您在这里不仅可以有所收获,同时也能感受到一份轻松欢乐的氛围,祝你生活愉快! 💝Ὁ…...

java面向对象(上)
一.面向对象与面向过程 1.面向过程 面向过程(procedure Oriented Programming),简称POP,主要思想就是将问题分解成一个个步骤去解决,把这个步骤称为函数. 典型语言:C语言 优点:可以大大简化代码 缺点:当代码量过大时,不方便维护 2.面向对象 面向对象(Object Oriented Pr…...

揭示SOCKS5代理服务器列表的重要性
在复杂的网络安全领域中,SOCKS5代理在保护在线活动方面发挥着关键作用。本文深入探讨了SOCKS5代理服务器列表的细节,探讨了它们的应用、优势以及在增强在线安全和隐私方面不可或缺的功能。 一、理解SOCKS5代理服务器列表 作为在客户端和服务器之间进行通…...

机器学习python实践——关于ward聚类分层算法的一些个人心得
最近在利用python跟着参考书进行机器学习相关实践,相关案例用到了ward算法,但是我理论部分用的是周志华老师的《西瓜书》,书上没有写关于ward的相关介绍,所以自己网上查了一堆资料,都很难说清楚ward算法,幸…...
从零制作一个docker的镜像
近期docker的镜像仓库不好用了,很多国内的源也无法使用了,所有今天给大家分享一下怎么从零制作一个CentOS镜像。 准备CentOS7最小环境 mkdir /centos7.9-root# 在该目录准备centos的最小环境 sudo yum --installroot/centos7.9-root --releasever7 ins…...

eclipse 老的s2sh(Struts2+Spring+Hibernate) 项目 用import导入直接导致死机(CPU100%)的解决
1、下载Apache Tomcat - Apache Tomcat 8 Software Downloads 图中是8.5.100的版本,下面的设置用的是另一个版本的,其实是一样。 2、先将Server配好,然后再进行导入操作。 2、选择jdk 当然,这里也可以直接“Download and instal…...

《米小圈动画汉字》汉字教育动画化:传统与创新的完美融合!
汉字,作为中华文化的瑰宝,承载着千百年来中华民族的智慧和思想。每一个汉字不仅仅是一个符号,更是一段历史的见证,一种文化的传承。在当今全球化的背景下,汉字教育面临着新的挑战与机遇。在这种背景下,如何…...

【LeetCode最详尽解答】11-盛最多水的容器 Container-With-Most-Water
欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家! 链接: 11-盛最多水的容器 直觉 这个问题可以通过可视化图表来理解和解决。 通过图形化这个…...

redis 缓存jwt令牌设置更新时间 BUG修复
大家好,今天我又又又来了,hhhhh。 上文中 我们永redis缓存了token 但是我们发现了 一个bug ,redis中缓存的token 是单用户才能实现的。 就是 我 redis中存储的键 名 为token 值 是jwt令牌 ,但是如果 用户a 登录 之后 创建一个…...
nginx精准禁止特定国家或者地区IP访问
1、安装依赖 dnf -y install gcc-c libtool gd-devel pcre pcre-devel openssl openssl-devel zlib zlib-devel libmaxminddb-devel pcre-devel zlib-devel gcc gcc-c make git2、获取NGINX安装包并安装 wget https://nginx.org/download/nginx-1.26.1.tar.gz git clone http…...

单片机课设-基于单片机的电子时钟设计(仿真+代码+报告)
基于单片机的电子时钟设计 前言一、课设任务是什么?二、系统总体方案硬件设计2.1 系统硬件总体设计2.2 键盘电路设计2.3 DS1302实时时钟芯片电路设计2.4 复位电路2.5 LCD电路设计 三、软件设计3.1 主程序流程图3.2 主要程序设计代码3.3 修改时间函数3.4 扫描键盘函数 四、仿真…...

.net 6 api 修改URL为小写
我们创建的api项目,url是[Route(“[controller]”)],类似这样子定义的。我们的controller命名是大写字母开头的,显示在url很明显不是很好看(url不区分大小写)。转换方式: var builder WebApplication.Crea…...

Windows电脑部署Jellyfin服务端并进行远程访问配置详细教程
文章目录 前言1. Jellyfin服务网站搭建1.1 Jellyfin下载和安装1.2 Jellyfin网页测试 2.本地网页发布2.1 cpolar的安装和注册2.2 Cpolar云端设置2.3 Cpolar本地设置 3.公网访问测试4. 结语 前言 本文主要分享如何使用Windows电脑本地部署Jellyfin影音服务并结合cpolar内网穿透工…...
rsync同步目录脚本
假设有两台服务器的示例 IP 地址为: Server A: 192.168.1.100Server B: 192.168.1.200 现在来解释如何使用这个脚本进行服务器之间文件夹内容的同步,保留路径和服务器信息的抽象化。 1. 脚本文件位置和权限 假设脚本文件位于 /root/script.sh&#x…...

LeetCode 6. Z 字形变换
LeetCode 6. Z 字形变换 将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下: 之后,你的输出需要从左往右逐行读取,产生…...

RTC实时时钟
一、Unix时间戳 1、Unix 时间戳 (1)Unix 时间戳(Unix Timestamp)定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数,不考虑闰秒 (2)时间戳存储在一个秒计数器中,秒计数器为…...
WHAT - React 学习系列(一)
官方文档 If you have a lot of HTML to port to JSX, you can use an online converter. You’ll get two things from useState: the current state (count), and the function that lets you update it (setCount). To “remember” things, components use state.To mak…...

代理模式(静态代理/动态代理)
代理模式(Proxy Pattern) 一 定义 为其他对象提供一种代理,以控制对这个对象的访问。 代理对象在客户端和目标对象之间起到了中介作用,起到保护或增强目标对象的作用。 属于结构型设计模式。 代理模式分为静态代理和动态代理。…...

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...

【开发技术】.Net使用FFmpeg视频特定帧上绘制内容
目录 一、目的 二、解决方案 2.1 什么是FFmpeg 2.2 FFmpeg主要功能 2.3 使用Xabe.FFmpeg调用FFmpeg功能 2.4 使用 FFmpeg 的 drawbox 滤镜来绘制 ROI 三、总结 一、目的 当前市场上有很多目标检测智能识别的相关算法,当前调用一个医疗行业的AI识别算法后返回…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
重启Eureka集群中的节点,对已经注册的服务有什么影响
先看答案,如果正确地操作,重启Eureka集群中的节点,对已经注册的服务影响非常小,甚至可以做到无感知。 但如果操作不当,可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...
Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?
在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
NPOI操作EXCEL文件 ——CAD C# 二次开发
缺点:dll.版本容易加载错误。CAD加载插件时,没有加载所有类库。插件运行过程中用到某个类库,会从CAD的安装目录找,找不到就报错了。 【方案2】让CAD在加载过程中把类库加载到内存 【方案3】是发现缺少了哪个库,就用插件程序加载进…...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...