当前位置: 首页 > news >正文

【Effective C++详细总结】第二章 构造/析构/赋值运算

✍个人博客:https://blog.csdn.net/Newin2020?spm=1011.2415.3001.5343
📚专栏地址:C/C++知识点
📣专栏定位:整理一下 C++ 相关的知识点,供大家学习参考~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪
🎏唠叨唠叨:在这个专栏里我会整理一些琐碎的 C++ 知识点,方便大家作为字典查询~

二、构造/析构/赋值运算

条款05:了解 C++ 默默编写并调用了哪些函数

在创建类时,如果自己不定义默认构造,拷贝构造(拷贝运算符),析构函数,那么编译器会自动生成这些函数。

//拷贝运算符:
classname& operator=(const classname& cn){......}

但是有些情况下编译器不会自动生成,拿下面这段代码举例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gj8IMl8Z-1678928093786)(《Effective C++》笔记.assets/image-20230305154051407.png)]

可以发现由于 class 里出现了引用类型和 const 类型,故编译器就不会自动生成拷贝复制和移动赋值函数。

这是因为引用类型是引用其他地方的内容,如果调用拷贝赋值和移动赋值可能会顺带改变引用处的值。

而 const 类型本身就是不可改动的,所以不会生成拷贝赋值和移动赋值函数。

另外 mutex 本身不能被拷贝和移动,所以拷贝构造和移动构造函数也不会自动生成。

条款06:若不想使用编译器自动生成的函数,就应该明确拒绝

对于类中拷贝构造函数,我们应当阻止他们。但若是不声明,编译器也会自动生成拷贝构造函数。

在现代 c++ 中我们可以通过 delete 关键字对编译器自动生成的函数进行删除,如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UEMTv6xR-1678928093789)(《Effective C++》笔记.assets/image-20230306121822807.png)]

但在之前的 C++ 中并没有该关键字,所以可以用私有化的方式进行。

class person
{
private:person(const person&);person& operator=(const person&);//参数是不必要写的,毕竟这个函数不会被实现
public:......
};

编译器自动生成的函数都是 public 函数,所以我们将 public 改为 private,就可以防止对象调用拷贝构造。

注:private 只有成员函数和友元函数可以调用。

同时也产生了一个问题,如何防止拷贝在成员函数或友元函数中被调用?

答案是建立一个父类,在父类中定义 private 拷贝函数,子类( person 等等)继承父类。因为子类不可以调用父类的 private 函数:

class uncopyable
{
private:uncopyable(const uncopyable&);uncopyable operator=(const uncopyable&);
};class person{......};

条款07:为多态基类声明virtual析构函数

多态把父类当作一个接口,用以处理子类对象:利用父类指针,指向一个在堆区开辟的子类对象。

class person
{
public:person();......~person();
};class teacher: public person{......};person* p = new teacher(...); 
...
delete p;
//在堆区开辟的数据要手动删除

上述代码是有问题的。

我们知道,在普通类继承里,删除子类对象会先调用子类的析构,再调用父类的析构。但在多态里情况有所不同。我们删除的是父类指针,调用的只是父类的析构函数,子类析构不会被调用,也就是说,子类对象没有被删除,而指针却没了。这是局部销毁,会造成资源泄漏等错误。

幸运的是,我们可以通过虚函数来解决这个问题。

在多态里,虚函数可以让子类重写父类的函数,同时在虚函数表中生成一个指针,找到子类重写函数的地址,从而让我们可以通过父类访问子类重写的函数。

class person
{
public:person();......virtual ~person();
};class teacher: public person{......};person* p = new teacher(...); 
...
delete p;
//删除 p 的时候调用 virtual ~person();
//virtual 找到子类析构函数的地址,导致子类也可以被删除

纯虚函数使得父类更像一个接口,这里不用多说。

注:多态里父类应该至少有一个虚函数(virtual 析构),若不用做多态,则类里不应该有虚函数。

条款08:别让异常逃离析构函数

释义:在析构函数内部处理异常

我们来看以下案例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sW0npgKS-1678928093790)(《Effective C++》笔记.assets/image-20230306123852685.png)]

如果在 db.close() 处发生异常,则会导致不可预料的情况。

首先介绍一下异常处理的办法:

try
{...}
//try 内部写可能产生异常的语句,没有产生异常,则catch语句不执行,产生则一一匹配
//catch 用于捕获并处理异常,和 case 有异曲同工之妙
catch(...)
{1、可以使用 abort(); 函数终止程序2、可以吞下这个异常,在 catch 内部做一些处理
}

了解如何处理异常之后,我们就可以实现如条款所说,在析构函数内部处理异常

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NGGJsYF8-1678928093792)(《Effective C++》笔记.assets/image-20230306124023644.png)]

上面左边的方法是直接调用 abort 终止程序,右边则是直接吞下异常,只是记录个日志,后面再处理。

但是这两种方法都有个缺点就是用户无法参与操作,因此可以写成下面的方式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xIVP9x75-1678928093806)(《Effective C++》笔记.assets/image-20230306124236886.png)]

用户可以自己实现一个 close 函数来进行关闭,如果关闭的顺利则 closed=true,反之关闭失败则会进行异常捕捉,在析构函数中帮助用户关闭。

条款09:绝不在构造和析构函数中使用虚函数

众所周知,在类的操作中,父类比子类先构造,而子类也比父类先析构(多态也是如此,多态先通过 virtual 找到子类析构,再析构父类),所以在构造父类的时候,子类对象还未进行初始化,在析构父类的时候,子类已经被销毁。来看下面这个例子:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48pkwFU8-1678928093808)(《Effective C++》笔记.assets/image-20230306125300951.png)]

此时,如果父类的构造和析构函数中有 virtual,则该函数无法找到子类的地址(或者说无视子类,因为子类被销毁/未被初始化),使程序发生不明确的行为。

可以发现上面我是想调用派生类的构造函数和析构函数,但是调用的却是基类的。如果想满足该要求,我们可以去掉虚函数,而是在基类接收一个参数来实现。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmuRPNIP-1678928093809)(《Effective C++》笔记.assets/image-20230306125520145.png)]

条款10:令 operator= 返回一个 reference to *this

释义:让赋值运算符重载版本返回一个自身,以便实现链式编程。

class employee{
public:int m_salary;employee(int a)//有参构造,赋工资初值{this->m_salary = a;}employee& operator=(const employee& ep){this->m_salary = ep.m_salary;return *this;}//返回其本身
};employee e1(5000);employee e2(50000);employee e3(123456);e1 = e2 = e3;//链式编程

条款11:在 operator= 中处理自我赋值

我们来看一段代码:

class person{...};
person p;
p = p;

这是自我赋值,这种操作看起来有点愚蠢,但是并不很难发生。

比如,一个对象可以有很多种别名,客户不经意间让这些别名相等;

或者如之前所说,父类的指针/引用指向子类的对象,也会造成一些自我赋值的问题。

自我赋值往往没有什么意义,还会有不安全性。

class student{...};
class teacher
{...
private:student* s;
};teacher& teacher::operator=(const teacher& teach)
{if(s != NULL){delete s;s = NULL;}s = new student(*teach.s);return *this;//便于链式操作
}

上述代码是不安全的。如果 *this 和 teach 是同一个对象,那么客户在删除 *this 的时候,也把 teach 删除了,s 就会指向一个被删除的对象,这是不允许的。

我们提供三种方法以解决这个问题:

1、证同检测:

teacher& teacher::operator=(const teacher& teach)
{if (this == &teach)return *this;//证同检测if (s != NULL){delete s;s = NULL;}s = new student(*teach.s);return *this;//便于链式操作
}

遗憾的是,证同检测可以保证自我赋值的安全性,但是不能保证“异常安全性”。即,如果 new student 抛出异常,则 s 就会指向一个被删除的对象,这是一个有害指针,我们无法删除,甚至无法安全读取它。

2、记住原指针:

teacher& teacher::operator=(const teacher& teach)
{student* stu = s;            //记住原指针if(s != NULL){delete s;s = NULL;}s = new student(*teach.s);   //如果抛出异常,s 也可以找回原来地址delete stu;                  //删除指针return *this;//便于链式操作
}

3、copy and swap:

void swap(const teacher& teach)
{......}teacher& teacher::operator=(const teacher& teach)
{teacher temp(teach);    //拷贝一个副本swap(temp);             //将副本和 *this 交换 return *this;//便于链式操作
}

交换操作不要考虑原本指针内容,可以保证赋值安全性,同时也能保证异常安全性。

条款12:复制对象时勿忘其每一个成分

释义:自定义拷贝函数时,要把类变量写全(子类拷贝不要遗漏父类的变量)。

父类变量通常存储在 private 里,子类不能访问父类 private 对象,所以应该调用父类的构造函数。

class animal
{
public:animal(const animal& an){......}animal& opeartor=(const animal& an){......}
......
private:string typename;
};class cat: public animal
{
public:cat(const cat& c);cat& operator=(const cat& c);private:string cat_type;
};cat::cat(const cat& c):cat_type(c.cat_type),//为了不遗漏父类变量,调用父类函数animal(c)
{}cat::cat& operator=(const cat& c)
{//为了不遗漏父类变量,调用父类函数animal::operator=(c);this->cat_type = c.cat_type;return *this;
}

值得注意的是,上面代码 copy 函数和 “=” 运算符调用的都是和本身一样的函数。究其原因,copy 函数是创建一个新的对象,operator= 是对已经初始化的对象进行操作。

我们不能用 copy 调用 operator=,因为这相当于用构造函数初始化一个新对象(父类尚未构造好)。

同理,也不能用 operator= 调用 copy,这相当于构造一个已经存在的对象(父类已经存在了)。

参考资料:
《Effective C++》侯捷:https://book.douban.com/subject/1842426/
EFFECTIVE C++ (万字详解)(一)_c++ effective:https://blog.csdn.net/qq_62674741/article/details/124896986
一文整理Effective C++ 55条款内容(全):https://blog.csdn.net/weixin_45926547/article/details/121276226?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2defaultCTRLISTRate-1-121276226-blog-124896986.pc_relevant_multi_platform_whitelistv3&utm_relevant_index=2

相关文章:

【Effective C++详细总结】第二章 构造/析构/赋值运算

✍个人博客:https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 📚专栏地址:C/C知识点 📣专栏定位:整理一下 C 相关的知识点,供大家学习参考~ ❤️如果有收获的话,欢迎点赞👍…...

webpack基础

webpack基础 webpack基础目录webpack基础前言Webpack 是什么?Webpack 有什么用?一、webpack的基本使用webpack如何使用文件和文件夹创建创建文件下载依赖二、基本配置5 大核心概念准备 Webpack 配置文件修改配置文件处理样式资源处理图片资源修改输出资源…...

jQuery《一篇搞定》

今日内容 一、JQuery 零、 复习昨日 1 写出至少15个标签 2 写出至少7个css属性font-size,color,font-familytext-algin,background-color,background-image,background-sizewidth,heighttop,bottom ,left ,rightpositionfloatbordermarginpadding 3 写出input标签的type的不…...

Spring Cloud学习笔记【负载均衡-Ribbon】

文章目录什么是Spring Cloud RibbonLB(负载均衡)是什么Ribbon本地负载均衡客户端 VS Nginx服务端负载均衡区别?Ribbon架构工作流程Ribbon Demo搭建IRule规则Ribbon负载均衡轮询算法的原理配置自定义IRule新建MyRuleConfig配置类启动类添加Rib…...

第九章:C语言数据结构与算法初阶之堆

系列文章目录 文章目录系列文章目录前言一、堆的定义二、堆的实现三、堆的接口函数1、初始化2、销毁3、插入4、删除5、判空6、元素个数四、堆排序1、建堆2、排序五、堆的应用——TOPK1、什么是TOPK问题?2、解决方法总结前言 堆就是完全二叉树。 一、堆的定义 我们…...

Mysql架构初识

🥲 🥸 🤌 🫀 🫁 🥷 🐻‍❄️🦤 🪶 🦭 🪲 🪳 🪰 🪱 🪴 🫐 🫒 🫑…...

字符串函数和内存函数

🍕博客主页:️自信不孤单 🍬文章专栏:C语言 🍚代码仓库:破浪晓梦 🍭欢迎关注:欢迎大家点赞收藏关注 字符串函数和内存函数 文章目录字符串函数和内存函数前言1. 字符串函数介绍1.1 s…...

Web3中文|GPT-4超越GPT-3.5的五大看点

A Beautiful CinderellaDwelling EagerlyFinally Gains HappinessInspiring Jealous KinLove Magically Nurtures Opulent PrinceQuietly RescuesSlipper TriumphsUniting Very WondrouslyXenial Youth Zealously这是一段描述童话故事《灰姑娘》的内容,它出自GPT-4之…...

动态矢量瓦片缓存库方案

目录 前言 二、实现步骤 1.将数据写入postgis数据库 2.将矢量瓦片数据写入缓存库 3.瓦片接口实现 4.瓦片局部更新接口实现 总结 前言 矢量瓦片作为webgis目前最优秀的数据格式,其主要特点就是解决了大批量数据在前端渲染时出现加载缓慢、卡顿的问题&#xff0…...

628.三个数的最大乘积

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。 示例 1: 输入:nums [1,2,3] 输出:6 示例 2: 输入:nums [1,2,3,4] 输出:24 示例 3: …...

【数据结构】堆和集合笔记

自己写一个堆首先,明确一下,为什么需要堆?>考虑插入,删除,查找的效率。数组,查找,最快是二分查找O(lgN)。但查找完如果要做什么操作,比如删除,就要挪动元素了。所以合…...

java LinkedList 源码分析(通俗易懂)

目录 一、前言 二、LinkedList类简介 三、LinkedList类的底层实现 四、LinkedList类的源码解读 1.add方法解读 : 〇准备工作 。 ①跳入无参构造。 ②跳入add方法。 ③跳入linkList方法。 ④增加第一个元素成功。 ⑤向链表中添加第二个元素。 2.remove方法解读 : 〇准备工…...

Vue中实现路由跳转的三种方式详细分解

vue中实现路由跳转的三种方式 目录 vue中实现路由跳转的三种方式 一、使用vue-router 1.下载vue-router模块到当前工程 2.在main.js中引入VueRouter函数 3.添加到Vue.use()身上 – 注册全局RouterLink和RouterView组件 4.创建路由规则数组 – 路径和组件名对应关系 5…...

全国自学考试03708《中国近现代史纲要》重点复习精要

1. 西方列强的殖民扩张和鸦片战争的影响。(两面性) :反面—破坏了了中国的小农经济,是中国由封建社会转变为两半社会。 --一系列不公平条约,破坏了中国主权领土完整。 --压迫中国人民,给中国人民带来了巨大…...

数据库面试题——锁

了解数据库的锁吗? 锁是数据库系统区别于文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问。 InnoDB下两种标准行级锁: 共享锁(S Lock),允许事务读一行数据。 排他锁(X Lock&…...

Python笔记 -- 文件和异常

文章目录1、文件1.1、with关键字1.2、逐行读取1.3、写入模式1.4、多行写入2、异常2.1、try-except-else2.2、pass1、文件 1.1、with关键字 with关键字用于自动管理资源 使用with可以让python在合适的时候释放资源 python会将文本解读为字符串 # -*- encoding:utf-8 -*- # 如…...

蓝桥杯刷题冲刺 | 倒计时24天

作者:指针不指南吗 专栏:蓝桥杯倒计时冲刺 🐾马上就要蓝桥杯了,最后的这几天尤为重要,不可懈怠哦🐾 文章目录1.修剪灌木2.统计子矩阵1.修剪灌木 题目 链接: 修剪灌木 - 蓝桥云课 (lanqiao.cn) 找…...

真正理解微软Windows程序运行机制——什么是消息

我是荔园微风,作为一名在IT界整整25年的老兵,今天说说Windows程序的运行机制。经常被问到MFC到底是一个什么技术,为了解释这个我之前还写过帖子,但是很多人还是不理解。其实这没什么,我在学生时代也被这个问题困绕过。…...

HTTP 缓存的工作原理

缓存是解决http1.1当中的性能问题主要手段。缓存可能存在于客户端浏览器上,也可以存在服务器上面,当使用过期缓存可能给用户展示的是错误的信息而导致一些bug。 HTTP 缓存:为当前请求复用前请求的响应 • 目标:减少时延&#xff1…...

RK3568在Android上进行驱动模块开发(源码外)

文章目录 前言一、ARCH架构二、编译器三、建立自己的Makefile文件总结前言 本文记录在驱动开发时,由于编译内核时间较长,经常会选择单独编译一个模块,这里主要讲解,makefile文件如何编写(主要是编译器和架构) 提示:以下是本篇文章正文内容,下面案例可供参考 一、ARCH…...

【网络】每天掌握一个Linux命令 - iftop

在Linux系统中,iftop是网络管理的得力助手,能实时监控网络流量、连接情况等,帮助排查网络异常。接下来从多方面详细介绍它。 目录 【网络】每天掌握一个Linux命令 - iftop工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告(肿瘤大小、血液指标),你需要做出一个**决定性判断**:恶性还是良性?这种“非黑即白”的抉择,正是**逻辑回归(Logistic Regression)** 的战场&a…...

使用分级同态加密防御梯度泄漏

抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...

Cinnamon修改面板小工具图标

Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

Keil 中设置 STM32 Flash 和 RAM 地址详解

文章目录 Keil 中设置 STM32 Flash 和 RAM 地址详解一、Flash 和 RAM 配置界面(Target 选项卡)1. IROM1(用于配置 Flash)2. IRAM1(用于配置 RAM)二、链接器设置界面(Linker 选项卡)1. 勾选“Use Memory Layout from Target Dialog”2. 查看链接器参数(如果没有勾选上面…...

【git】把本地更改提交远程新分支feature_g

创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...

Hive 存储格式深度解析:从 TextFile 到 ORC,如何选对数据存储方案?

在大数据处理领域,Hive 作为 Hadoop 生态中重要的数据仓库工具,其存储格式的选择直接影响数据存储成本、查询效率和计算资源消耗。面对 TextFile、SequenceFile、Parquet、RCFile、ORC 等多种存储格式,很多开发者常常陷入选择困境。本文将从底…...

【Go语言基础【13】】函数、闭包、方法

文章目录 零、概述一、函数基础1、函数基础概念2、参数传递机制3、返回值特性3.1. 多返回值3.2. 命名返回值3.3. 错误处理 二、函数类型与高阶函数1. 函数类型定义2. 高阶函数(函数作为参数、返回值) 三、匿名函数与闭包1. 匿名函数(Lambda函…...

在Mathematica中实现Newton-Raphson迭代的收敛时间算法(一般三次多项式)

考察一般的三次多项式,以r为参数: p[z_, r_] : z^3 (r - 1) z - r; roots[r_] : z /. Solve[p[z, r] 0, z]; 此多项式的根为: 尽管看起来这个多项式是特殊的,其实一般的三次多项式都是可以通过线性变换化为这个形式…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...