Effective C++ 改善程序与设计的55个具体做法笔记与心得 4

四. 设计与声明

18. 让接口容易被正确使用,不易被误用

请记住

  • 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质
  • “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
  • “阻止误用”的办法包括建立新类型、限制类型上的操作、束缚对象值,以及消除客户的资源管理责任。
  • trl::shared_ptr支持定制型删除器。这可防范DLL问题,可被用来自动解除互斥锁等等。

解释
‌‌‌‌  设计优秀的接口确实要注重以下几个方面:

  1. 易于正确使用:一个良好设计的接口应该使用户能够更容易地使用它。这需要避免在接口设计中的歧义和不一致,并尽量与已有的、用户熟悉的模式保持一致。例如,对一致性和对内置类型行为的兼容都属于这种情况。

  2. 不容易被误用:我们也应确保接口能够防止用户误用。创建新的类型(以区分不同的概念或值),限制类型上的操作,约束对象的值,或者管理客户端的资源,都是有效的防止误用手段。

‌‌‌‌  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命名空间内添加全新的内容是不被允许的,因为这可能引发未定义的行为。

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

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

相关文章

ENVI实战—一文搞定监督分类

实验1&#xff1a;利用ROI建立样本训练集和验证集 目的&#xff1a;学会利用ROI建立计算机分类时的样本集 过程&#xff1a; ①导入影像&#xff1a;打开ENVI&#xff0c;选择“打开→打开为→光学传感器→ESA→Sentinel-2”&#xff0c;将Sentinel-2影像导入到ENVI平台中。…

6.20学习总结

D - 刻录光盘https://vjudge.net/problem/%E6%B4%9B%E8%B0%B7-P2835 思路&#xff1a; 利用并查集和弗洛伊德&#xff0c;对需要传递的对象都进行标记&#xff0c;经过处理后使他们的父亲发生相应的改变&#xff0c;最后对数组进行查询累加即可 代码&#xff1a; #include<…

uni-app的uni-list列表组件高效使用举例 (仿知乎日报实现)

目录 前言 uni-list组件介绍 基本使用 高级配置与自定义 仿知乎日报实现 知乎的api接口 后台服务实现 知乎日报首页 轮播图界面实现 客户端接口实现 uni-list列表使用 插入日期分割线 下滑分页的实现 完整页面代码 其他资源 前言 在移动应用开发领域&#xff0…

chatgpt: linux 下用纯c 编写ui

在Linux下用纯C语言编写用户界面&#xff08;UI&#xff09;&#xff0c;通常会使用GTK或Xlib。GTK是一个更高级的库&#xff0c;提供了丰富的控件和功能&#xff0c;而Xlib则是一个更底层的库&#xff0c;提供了直接操作X Window系统的功能。 下面是一个使用GTK在Linux上创建…

1.3自然语言的分布式表示-word2vec

文章目录 0基于计数的方法的问题1什么是基于推理的方法2神经网络中单词的表示2.1 MatMul 层的实现 3简单word2vec的实现3.1 CBOW模型的结构3.1.1神经元视角3.1.2层的视角3.1.3多层共享权重时存在的问题 3.2 CBOW模型的学习3.3单词的分布式表示 代码都位于&#xff1a;nlp&#…

【机器学习 复习】第4章 决策树算法(重点)

一、概念 1.原理看图&#xff0c;非常简单&#xff1a; &#xff08;1&#xff09;蓝的是节点&#xff0c;白的是分支&#xff08;条件&#xff0c;或者说是特征&#xff0c;属性&#xff0c;也可以直接写线上&#xff0c;看题目有没有要求&#xff09;&#xff0c; &#xff…

报错:ZeroDivisionError_ division by zero

问题&#xff1a;除数为0 原代码错误来源 # 归一化 , 保留6位小数 w round(w / img_w, 6) h round(h / img_h, 6) cx round(cx / img_w, 6) cy round(cy / img_h, 6) # print(cls_id, cx, cy, w, h) # 结果保存到数据labels文件夹中的txt文件 out_file.write(str(cls_id) …

Linux tcpdump详解

目录 前言&#xff1a;BPF伯克利包过滤器介绍1.BPF语法&#xff08;tcpdump语法&#xff09;2.逻辑运算符3.常用的原子条件1. 协议相关的原子条件2. 地址相关的原子条件3. 端口相关的原子条件4. 网络层和链路层(mac地址&#xff09;原子条件5. 广播和多播6. VLAN 相关的原子条件…

com.lowagie:itext:jar:2.1.7.js9 was not found

1 在 https://jaspersoft.jfrog.io/ui/native/third-party-ce-artifacts/com/lowagie/itext/2.1.7.js9/下载com/lowagie/itext/2.1.7.js9/itext-2.1.7.js9.jar的包&#xff0c; 2 在本地maven仓库com.lowagie.itext.2.1.7的目录下&#xff0c;将itext-2.1.7.js9.jar复制更名为…

深度学习 --- stanford cs231学习笔记五(训练神经网络的几个重要组成部分之二,数据的预处理)

训练神经网络的几个重要组成部分 二 2 Data Preprocessing数据的预处理 数据预处理的几种方法 2&#xff0c;1 数据的零点中心化 数据的零点中心化的目的就是为了把数据的整体分布拉回到原点附近&#xff0c;也就是让数据的整体均值变为0。 ​ 2&#xff0c;2 数据的标准化 数…

JDBC(简介、入门与IDEA中导入MySQL的驱动)

&#xff08;建议学完 MySQL 的基础部分&#xff09; JDBC——简而言之&#xff1a;用 Java 语言操作数据库。 Java DataBase Connectivity&#xff08;Java 语言连接数据库&#xff09; 目录 一、引言 &#xff08;1&#xff09;基本介绍 &#xff08;2&#xff09;JDBC 简…

力扣739.每日温度

力扣739.每日温度 单调栈 从右到左做栈中存下标 class Solution {public:vector<int> dailyTemperatures(vector<int>& temperatures) {int n temperatures.size();vector<int> ans(n);stack<int> st;for(int in-1;i>0;i--){int t tempera…

[信号与系统]傅里叶变换、卷积定理、和为什么时域的卷积等于频域相乘。

前言 最近学习以下IIR滤波器和FIR滤波器 前置 1. 时域和频域 时域和频域代表着频率和时间与振幅的一一对应关系 2. 卷积运算 关于卷积的定义&#xff0c;详情请看 这篇文章能让你明白卷积 卷积运算是一种数学运算&#xff0c;广泛应用于信号处理、图像处理、控制系统和概…

【ARMv8/v9 GIC 系列 2 -- GIC SPI 中断的 enable和 disable 配置】

文章目录 GIC 中断 Enable 和 DisableGICD_ISENABLER<n>GICD_ICENABLER<n>参数 n使用举例代码实现注意事项 GIC 中断 Enable 和 Disable 在ARMv8架构中&#xff0c;通用中断控制器&#xff08;GIC&#xff09;负责管理处理器的中断。为了控制和管理这些中断&#…

TSF的服务发现与Consul有何区别?

TSF(腾讯服务框架)和Consul都是用于服务发现的工具,但它们在设计理念、功能特性、集成方式等方面存在一些区别。 ### 设计理念和目标 **Consul** 是一个开源的工具,用于服务发现、配置和分段。它提供了一种简单的方式来注册和发现服务,以及健康检查和键值存储功能。Consul…

PyTorch实现NMS算法

PyTorch实现NMS算法 介绍示例代码 介绍 参考链接1&#xff1a;NMS 算法源码实现 参考链接2&#xff1a; Python实现NMS&#xff08;非极大值抑制&#xff09;对边界框进行过滤。 目标检测算法&#xff08;主流的有 RCNN 系、YOLO 系、SSD 等&#xff09;在进行目标检测任务时&…

网络安全管理组织架构复习

文章目录 安全管理机构岗位设置安全要求要求解读 安全管理机构 安全管理的重要实施条件就是有一个统一指挥、协调有序、组织有力的安全管理机构,这是网络安全管理得以实施、推广的基础。 通过构建从单位最高管理层到执行层及具体业务运营层的组织体系&#xff0c;可以明确各个…

【HarmonyOS4学习笔记】《HarmonyOS4+NEXT星河版入门到企业级实战教程》课程学习笔记(十四)

课程地址&#xff1a; 黑马程序员HarmonyOS4NEXT星河版入门到企业级实战教程&#xff0c;一套精通鸿蒙应用开发 &#xff08;本篇笔记对应课程第 22 节&#xff09; P22《21.ArkUI-实现摇杆功能》 本节我们将小鱼动画案例中的按钮控制改为摇杆控制&#xff0c;用来熟悉和巩固…

【多模态论文】CLIP(Contrastive Language-Image Pre-training)

论文&#xff1a;Learning Transferable Visual Models From Natural Language Supervision 链接&#xff1a;https://arxiv.org/abs/2103.00020 摘要 问题&#xff1a; 对预定的类别进行预测&#xff0c;这种有监督的训练形式受限于额外标记数据 。如何利用图像的原始文本来获…

图像数字化基础

一、像素 1、获取图像指定位置的像素 import cv2 image cv2.imread("E:\\images\\2.png") px image[291,218] print("坐标(291,218)上的像素的BGR值是&#xff1a;",px) &#xff08;1&#xff09;RGB色彩空间 R通道&#xff1a;红色通道 G通道&…