effective c++读书笔记4

news2024/11/25 16:34:56

设计class犹如设计type

新type的对象应该如何被创建和销毁?:这会影响到你的class的构造函数和析构函数以及内存分配函数和释放函数的设计。

对象的初始化和对象的赋值有什么样的区别?这答案决定你的构造函数和赋值操作符的行为,以及其间的差异。很重要的是别混淆了“初始化”和“赋值”,因为它们对应不同的函数调用。

新type的对象如果被passed by value(以值传递),意味着什么?记住,copy构造函数用来定义一个type的pass-by-value该如何实现。

什么是新type的“合法值”?对于class的成员变量而言,通常只有某些数值集是有效的。那些数值决定了你的class必须维护的约束条件,也就决定了你的成员函数必须进行的错误检查工作,它也影响函数抛出的异常、以及函数异常明细列。

你的新type需要配合某个继承体系吗?如果你继承自某些既有的classes,你就受到那些classes的设计的束缚,特别是受到“它们的函数是virtual或non-virtual”的影响。如果你允许其他classes继承你的class,那会影响你所声明的函数--尤其是析构函数,是否为virtual。

你的新type需要什么样的转换?如何你允许类型T1之物被隐式转换为类型T2之物,就必须在class T1内写一个类型装换函数或者在class T2内写一个non-expliccit-noe-argument(可被单一实参调用)的构造函数。如果你允许explicit构造函数存在,就得写出专门负责执行转换的函数,且不得为类型转换操作符或non-explicit-one-argument构造函数。

什么样的操作符合函数对此新type而言是合理的?这个取决于你将为你的class声明哪些函数。

什么样的标准函数应该驳回?那些正是你必须声明为private者。

谁该采用新type的成员?这个跟设计成员函数有关。你可以决定哪个成员为private,哪个为protected,哪个为private。

你的新type有多么一般化?或许你其实并非定义一个新type,而是定义一整个types家族。果真如此你就不应该定义一个新class,而是定义一个新的class template。

你真的需要一个新的type吗?如果只是定义新的派生类以便为既有的class添加机能,那么说不定单纯定义一或多个非成员函数或模板,更能够达到目标

宁以pass-by-reference-to-const替换pass-by-value

传值调用会造成代码执行效率低

例如:

可以看下传值过程中做了那些事情?

①执行6次构造函数:plato传入进函数的时候,需要调用1次Student的构造函数,Student构造函数执行前需要先构造Person ,因此还会调用1次Person类的构造函数。Person和Student两个类中共有4个string成员变量,因此还需要调用string的构造函数4次
②执行6次析构函数:当函数执行完成之后,传入validateStudent函数中的plato副本还需要执行析构函数,那么执行析构函数的时候分别要对应执行6次析构函数(Student+Person+4个string成员变量)

改进:

这种传递方式的效率高得多:没有任何构造函数或析构函数被调用,因为没有任何新对象被创建

以by reference方式传递参数也可以避免slicing(对象切割)问题

例如:

当执行下面代码时:

因为printNameAndDisplay函数的参数为Window类型,所以即使我们传入的是WindowsWithScrollBars类型的对象,那么这个对象会被截断,只取WindowsWithScrollBars对象中属于基类(Window)的内容,然后传递给函数
因此被截断之后,不论如何调用,printNameAndDisplay函数中调用的display函数总是Window的display虚函数,不会是WindowsWithScrollBars中的display虚函数。因为多态只会发生在基类指针/引用指向于派生类的情况下,此处没有指针/引用

改进:

对于内置类型建议传值调用

C++编译器的底层,是把引用以指针的形式实现,因此引用传递就是指针的传递

如果你使用的对象属于内置类型(例如int),内置类型都相当小,传值调用往往比引用传递的效率高些

这条规则在STL的迭代器和函数对象中都被广泛引用

请记住:

1.尽量以pass-by-reference-to-const替换 pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)。
2. 以上规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当。

必须返回对象时,别妄想返回其reference

自己要记住,任何时候看到一个reference声明式,你都应该立刻问自己,它的另一个名称是什么?

看下面一段代码:我们有一个用来变现有理数的class,内含一个函数用来计算两个有理数的乘积

下面的那一段代码是合理的:

但如果返回值是引用,会存在什么问题呢?看下面的一些调用:

最后的那段代码存在问题,期望“原本就存在一个其值为3/10的Rational对象”并不合理。如果operator*要返回一个reference指向如此数值,它必须自己创建那个Rational对象。

方案1:

stack 空间或在 heap空间创建对象,例如:

原因:由于result是局部对象,函数结束之后局部对象(栈空间)就释放了,引起返回其引用是无效的,也就是说该函数返回了一个已经无效的对象的引用。

原因:在函数内,我们在堆上申请了一个对象,然后返回对象的引用,虽然函数结束之后对象仍存在,我们使用起来不会出错,但是这个函数内部建立的对象我们无法释放(因为我们无法获取其指针,不能进行delete操作)。因此会造成内存泄漏

方案2:返回一个静态局部变量

例如:

再看下面的代码:先不管多线程安全的问题

表达式((a*b) =- (c*d))总是被核算为true,不论a, b, c和d的值是什么!

原因:在operator== 被调用前,已有两个operator*调用式起作用,每一个都返回reference指向operator*内部定义的static Rational对象。因此operator==被要求将“operator*内的 static Rational对象值”拿来和“operator*内的 staticRational对象值”比较,那么当我们每回调用operator*函数的时候,每回返回的都是同一个静态局部变量的引用。

请记住:

绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象。

宁以non-member,non-friend替换member函数

象有个class用来表示网页浏览器。这样的 class可能提供的众多函数中,有一些用来清除下载元素高速缓存区(cache of downloaded elements)、清除访问过的URLs的历史记录等,例如:

许多用户会想一整个执行所有这些动作,因此webBrowser也提供这样-一个函数:

那么是成员函数好还是非成员函数呢?

解释:

1.提供non-member函数可允许对webBrowser 相关机能有较大的包裹弹性( packaging flexibility),而那最终导致较低的编译相依度,增加 webBrowser的可延伸性。如果某些东西被封装,它就不再可见。愈多东西被封装,愈少人可以看到它。而愈少人看到它,我们就有愈大的弹性去变化它。
2.第二件值得注意的事情是,只因在意封装性而让函数“成为class 的 non-member”,并不意味它“不可以是另一个class 的member”

namespace和 classes不同,前者可跨越多个源码文件而后者不能。一个像webBrowser这样的class可能拥有大量便利函数,某些与书签(bookmarks>有关,某些与打印有关,分离它们的最直接做法就是将书签相关便利函数声明于一个头文件,将cookie相关便利函数声明于另一个头文件,以此类推。

这正是C++标准程序库的组织方式。

以此种方式切割机能并不适用于class成员函数,因为一个class必须整体定义,不能被分割为片片段段。一小部分。

将所有便利函数放在多个头文件内但隶属同一个命名空间,意味客户可以轻松扩展这一组便利函数。他们需要做的就是添加更多non-member non-friend函数到此命名空间内。

请记住:

宁可拿non-member non-friend 函数替换member函数。这样做可以增加封装性、包裹弹性(packaging flexibility)和机能扩充性

若所有参数皆需类型转换,请为此采用non-member函数

先看一段关于类型转换的代码:

如果这样去使用代码:

由于可以进行隐式的类型转换,因此我们还可以进行下面的有理数相乘

在上面的代码中,编译器知道你正传递一个int对象,但是operator*()需要的是Rational,因此它调用Rational构造函数并赋予你所提供的int,就可以构造出一个临时的Rational对象,但是如果这样使用:下面是错误的代码

oneHalf是一个内含operator*函数的class的对象,所以编译器调用该函数。然而整数2 并没有相应的class,也就没有operator*成员函数。编译器也会尝试寻找可被以下这般调用的non-member operator*(也就是在命名空间内或在global作用域内):

从上面的例子我们可以看到,如果operator*()为成员函数,在某些情况下即使存在隐式转换也不能成功执行。只有当参数被列于参数列(parameter list)内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”—--即 this对象—一的那个隐喻参数,绝不是隐式转换的合格参与者。

例如:

那么下面的调用都是正确的:

请记住:

1.如果你需要为某个函数的所有参数(包括被this这指针很所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member
2.如果一个函数不建议声明为member版本,那么就将该函数声明为non-member版本,而不是friend函数

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

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

相关文章

分享一套Springboot个人博客系统源码带本地搭建教程

Springboot个人博客系统源码带本地搭建教程,需要源码学习可私信我获取。 技术架构 前端框架:JQuery SemanticUI Markdown prism animatecss Tocbot zplayer lightbox 后端框架:SpringBoot 2.2.5 Mybatis Thymeleaf PageHelper m…

基于JAVA SSM springboot实现的抗疫物质信息管理系统设计和实现

基于JAVA SSM springboot实现的抗疫物质信息管理系统设计和实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 …

记录一次Spring事务线上异常

Spring事务管理配置方式: XML模糊匹配,绑定事务管理注解,可对每个需要进行事务处理的方法单独配置,只需 Transactional,然后添加属性配置 为简便,本文使用注解方式。Spring初始化时,会通过扫描…

C进阶:通讯录(动态版本 + 文件操作)附源码

本文主要讲解通讯录的代码; 需要拥有结构体,动态内存开辟,文件操作的知识; 目录 🐲一.通讯录思路 🕊️二.三个文件的建立 😼三.所需要使用的变量的创建(包含在头文件contact.h中&…

从0到1完成一个Vue后台管理项目(十三、信息列表页面实现:对话框新增、DateTimePicker 日期时间选择器)

往期 从0到1完成一个Vue后台管理项目(一、创建项目) 从0到1完成一个Vue后台管理项目(二、使用element-ui) 从0到1完成一个Vue后台管理项目(三、使用SCSS/LESS,安装图标库) 从0到1完成一个Vu…

JavaScript 事件流

文章目录JavaScript 事件流概述事件冒泡简介onclick() 事件冒泡addEventListener() 事件冒泡stopPropagation() 阻止事件冒泡事件捕获简介addEventListener() 事件捕获W3C标准事件流取消事件默认行为取消使用对象属性绑定的事件的默认行为取消使用addEventListener()绑定的事件…

社科院与杜兰大学金融管理硕士项目你有了解吗?每年招生一期错过申请太可惜了

社科院与杜兰大学金融管理硕士是个什么项目?社科院是所学校吗,怎么都没听说过。杜兰大学又是哪里的学校?前几天有位咨询的同学抛出这些疑问,着实让我不知如何给予回答。像社科院这么低调的院校太少了。社科院全称是中国社会科学院…

错题 5jxn 8253,neg指令,知CPU频率求经典总线周期,如何取一个字,字扩展指令CBW扩展要求,知道相对位移量求转移后指令偏移地址

1:8253工作于方式1时,输出负脉冲的宽度等于() A:计数初值N-1个CLK脉冲宽度 B:计数初值N1个CLK脉冲宽度C:计数初值N个CLK脉冲宽度 D:计数初值(2N-1)/2个CLK脉冲宽度 方式0和方式1 波形相同(计数过程中低,计数完高)&…

Pytorch 暂退法(Dropout)

在2014年,斯里瓦斯塔瓦等人 (Srivastava et al., 2014) 就如何将毕晓普的想法应用于网络的内部层提出了一个想法: 在训练过程中,他们建议在计算后续层之前向网络的每一层注入噪声。 因为当训练一个有多层的深层网络时,注入噪声只会…

八、Gtk4-GtkBuilder and UI file

1 New, Open and Save button 在上一节中,我们制作了一个非常简单的编辑器。它在程序开始时读取文件,在程序结束时将文件写出来。它可以工作,但不是很好。如果我们有“新建”、“打开”、“保存”和“关闭”按钮就更好了。本节介绍如何在窗口…

【年度总结】2022不忘初心,砥砺前行 2023纵有疾风起,人生不言弃。

2022 工作 砥砺前行 跨境电商跑路 2022年3月,从一家小跨境电商被动跑路,果断处于迷茫期,被动跑路的最好优势就是有赔偿,哈哈,怎么还有一丢丢高兴呢。简单总结了下在该公司的经历,是之前的老大带我去的&am…

如何通过产品帮助中心减轻客服压力,提高内部人员工作效率

客户的问题一直在重复,客服人员压力山大 客户不愿接听客服电话,产品问题难以解决 下班时间休息日,产品问题找谁问? 这些关于客户服务的老问题们困扰着许多产品方多年 要解决以上问题,更好地满足顾客需求。 搭建帮…

基于javaweb(springboot+mybatis)生活美食分享平台管理系统设计和实现以及文档报告

基于javaweb(springbootmybatis)生活美食分享平台管理系统设计和实现以及文档报告 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎…

详解函数指针(●‘◡‘●)☞

本文紧接于http://t.csdn.cn/78wbF 这篇一.函数指针数组\ ( >O< ) /1.书写形式&#xff1a;由函数指针内部*变量名>*变量名[n]&#xff1b; 2.使用&#xff1a;函数指针数组的用途&#xff1a;转移表 例如&#xff1a;模拟计算器&#xff1a;#include<stdio.h> …

用了这么久 IDEA,你还没用过 Live Templates 吗?

大家好&#xff0c;我是风筝&#xff0c;公众号「古时的风筝」&#xff0c;专注于 Java技术 及周边生态。 Live Templates 是什么&#xff0c;听上去感觉挺玄乎的。有的同学用过之后觉得简直太好用了&#xff0c;不能说大大提高了开发效率吧&#xff0c;至少也是小小的提高一下…

Qt创建项目:手把手创建第一个Qt项目

上一节介绍了QtCreator编辑器的页面长什么样子&#xff0c;以及都有哪些功能区&#xff0c;每个功能区都是用来做什么的。这一节我就手把手带大家创建一个Qt项目。 创建项目 点击新建按钮 创建项目有两个入口&#xff0c;一个是在欢迎页面的projects中点击New(新建)按钮&…

未来,勒索软件会呈现何种发展态势?

尽管过去一年里&#xff0c;全世界大约花费了1500亿美元在网络安全领域上&#xff0c;却无法真正阻止黑客攻击。在过去一年里&#xff0c;针对医院、学校、政府的勒索软件越来越多&#xff1b;加密货币领域也有无休止的黑客盗窃事件&#xff1b;还有针对微软、英伟达、Rockstar…

CORS跨域通信

在上一集的坐牢文章中&#xff0c;我们介绍了非官方的很多中方案&#xff0c;其中不乏一些江湖秘术。今天的这个&#xff0c;绝对的正统&#xff0c;纯正的官方打造。我们赶紧来看看。 1.什么是CORS&#xff1f; CORS 是一个 W3C 标准&#xff0c;全称是“跨域资源共享”&…

Prometheus的使用

Prometheus 是一个开放性的监控解决方案&#xff0c;用户可以非常方便的安装和使用 Prometheus 并且能够非常方便的对其进行扩展。 在Prometheus的架构设计中&#xff0c;Prometheus Server 并不直接服务监控特定的目标&#xff0c;其主要任务负责数据的收集&#xff0c;存储并…

ArcGIS Engine基础(31)之使用仿射变换对矢量数据进行空间校正

在生产数据过程中&#xff0c;因每个工程项目都可能有自己的施工坐标系&#xff0c;难免会产生数据提供方与数据使用方采用的坐标系不一致&#xff0c;造成数据在不同坐标系下存在一定偏移、旋转、缩放等&#xff0c;为了让数据能够在新坐标系准确定位&#xff0c;需要进行空间…