C++:构造/析构/赋值运算(Effective C++)

2023-11-09

写在前面

这是对Effective C++这本书中的部分内容进行的总结以及代码实践,主要是记录一些对我印象深刻的,确实能改善程序的内容,和有需要实践验证加深印象的一部分实践和我自己的理解

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

当我们创建了一个类后,即使这个类中并没有写任何成员函数,但是编译器依旧会为我们生成六个成员函数,也叫做六大默认成员函数

这六个成员函数分别为:

  1. 构造函数:用来完成初始化
  2. 析构函数:用来完成清理
  3. 拷贝构造:用来初始化对象
  4. 赋值重载:用来对象间的赋值
  5. 普通对象取地址重载:取地址操作符重载
  6. const对象取地址重载:取地址操作符重载

其中需要注意的是,编译器默认生成的构造函数是一个浅拷贝,只是把非静态成员进行值拷贝,关于浅拷贝和深拷贝前文有讲述,因此完全使用编译器生成的构造函数有时是不够的,需要我们自己完善编写

这里对静态成员变量也进行一下补充:

  • 静态成员变量是该类所有对象所共有的内容,在整个函数生命周期内只分配一次内存
  • 静态成员变量存储在全局变量区,与对象的创建销毁无关

因此编译器默认生成的拷贝构造函数只对non-static成员变量奏效

关于构造函数的建议:

编译器默认生成的构造函数是无参的版本,而在实际应用中常常需要用到带参数的构造函数,因此这里对于一个类来说,自己写一个全缺省的构造函数会便利很多

关于赋值操作符重载:

赋值重载函数也是默认成员函数,但编译器是可以拒绝的,只有当生出的代码合法,并且有适当机会证明有意义编译器才会生成

#include <iostream>
#include <string>
using namespace std;

template <class T>
class nameobject
{
public:
	nameobject(string& name, const T& value)
		:namevalue(name)
		,objectvalue(value)
	{}
private:
	string& namevalue;
	const T objectvalue;
};

int main()
{
	string newdog("Tom");
	string olddog("Jim");
	nameobject<int> a(newdog,1);
	nameobject<int> b(olddog,2);
	a = b;
	return 0;
}

对于上面的代码,编译器无法解析a=b究竟该如何解释,如果解释为将b中元素赋予给a,那么就违背了引用不能指向不同对象这一原则,编译器不知该如何进行操作,因此拒绝生成赋值重载函数,此时需要我们自己来完成这一函数

编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数

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

编译器默认会生成成员函数,但假设现在有这样的情景,我不允许外部使用类的时候调用拷贝构造和赋值重载函数,但如果我在类内不写编译器也会默认生成,此时应该如何解决?

解决方法是,将函数写到private类内

外部成员是不可以调用私有类成员的,因此将函数写到私有就可以解决这个问题

根据这个原理,可以考察一个有趣的面试题

如何定义一个只能在堆上或栈上生成对象的类?

  • 只能在堆上:将析构函数设置为私有,因为C++是静态绑定语言,编译器管理栈上对象的生命周期,编译器在为类对象分配栈空间时,会优先检查类的析构函数的可访问性,如果析构函数不可访问,就不能在栈上创建对象
  • 只能在栈上:将new和delete重载为私有,因为在堆上生成对象,使用new关键词操作,其过程分为两个阶段:首先new在堆上寻找可用内存,分配给对象,第二阶段利用构造函数生成对象。如果new操作设置为私有,那么第一阶段就无法完成,就不能在堆上生成对象

为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现

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

这个条款比较简单,令赋值操作符返回一个reference to *this

这只是一个协议,并没有强制性,如果你不遵循它也不会报错,但是这样可以提高效率,同时也算是一个共识,我们最好随众

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

赋值重载函数并没有想象那么简单,其中有很多小细节

如果有下面的代码:

#include <iostream>
#include <string>
using namespace std;

class B
{
public:
	B(int a=0,int b=1)
		:_a(a)
		,_b(b)
	{}
private:
	int _a;
	int _b;
};

class A
{
public:
	A()
	{
		tmp = new B;
	}
	A& operator=(const A& p)
	{
		delete tmp;
		tmp = new B(*p.tmp);
		return *this;
	}
private:
	B* tmp;
};

int main()
{
	A a1;
	A a2;
	a1 = a2;
	return 0;
}

现在来看是没有问题的,很正常的完成了赋值的操作,但事实上是有问题的

如果这里赋值重载函数中的*this和形参是同一个对象,那么此时在执行代码的时候就会销毁掉这个对象的内容,同时赋值的结果就会导致拥有一个被删除的对象,这是错误的行为

解决方案1

因此可以在函数前加一个if语句判断条件,如果形参和this指针相同,就直接返回this指针,这是一种解法

解决方案2

A& operator=(const A& p)
{
	B* cur = tmp;
	tmp = new B(*p.tmp);
	delete tmp;
	return *this;
}

交换一下语句的顺序,在复制前做到不要删除原来的数据即可避免这样的情况发生

解决方案3

但最好的方法,是利用所谓的copy-and-swap方法:

A& operator=(const A& p)
{
	A tmp(p);
	swap(tmp);
	return *this;
}

这相当于是一个很巧妙的方法,利用形参在类内直接构造出一个对象,再将这个对象和*this进行交换,则此时就把形参的数据很巧妙的拷贝到了this指针中,完成了赋值的目的,同时也解决了前面出现的问题

确保当对象自我赋值时operator=有良好的行为,其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap
确认任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确

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

当类内的私有成员增添了某些成员后,如果构造函数为自己构建,要补上对应的构造函数,编译器不会对此做出报错

Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”
不要尝试以某个copying函数实现另一个copying函数,如果有重叠部分可以通过第三个函数调用

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++:构造/析构/赋值运算(Effective C++) 的相关文章

  • 添加 Nullable int 时保持 null?

    我想添加可为空的int 并保留null当所有值都是null 我想要这个结果 1 2 3 1 null 1 null null null O null 0 问题是 如果我将一个值与 null 相加 结果为 null int i1 1 int
  • CMake 找不到请求的 Boost 库

    既然我已经浏览了其他人的解决方案几个小时 但找不到适合我的问题的正确答案 我想将我的具体问题带给您 我正在尝试使用 CMake 构建 vsomeip 为此 我之前构建了 boost 1 55 但是 我在 CMake 中收到以下错误 The
  • 在路由mvc 4中添加公司名称

    我一直在尝试为 Facebook 等用户提供在 URL 中添加公司名称的选项 http localhost 50753 MyCompany Login 我尝试过不同的网址 但没有成功 routes MapRoute name Default
  • 在 C# 中调用 C++ 库 [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我有很多用 C 编写的库 我想从 C 调用这些库 但是 我遇到了很多问题 我想知道是否有书籍或指南告诉我如何做到这一点 Dll导入 htt
  • 为什么'enable_if'不能用于禁用这里声明

    include
  • 将表(行)与 OpenXML SDK 2.5 保持在一起

    我想在 Word 文档中生成多个表 每行 2 行 但我想将这两行保留在一起 如果可能的话 new KeepNext 第一行不起作用 new KeepNext 第一行的最后一段不起作用 new CantSplit 放在桌子上不起作用 在所有情
  • C# 编译器不会优化不必要的强制转换

    前几天 在写答案的时候这个问题 https stackoverflow com questions 2208315 why is any slower than contains在这里 关于溢出 我对 C 编译器感到有点惊讶 它没有按照我的
  • 如何对 NServiceBus.Configure.WithWeb() 进行单元测试?

    我正在构建一个 WCF 服务 该服务接收外部 IP 上的请求并将其转换为通过 NServiceBus 发送的消息 我的单元测试之一调用Global Application Start 它执行应用程序的配置 然后尝试将 Web 服务解析为 验
  • 如何在三个 IEnumerable 上使用 Zip [重复]

    这个问题在这里已经有答案了 可能的重复 使用 Linq 从 3 个集合创建项目 https stackoverflow com questions 5284315 create items from 3 collections using
  • 引用/指针失效到底是什么?

    我找不到任何定义指针 引用无效在标准中 我问这个问题是因为我刚刚发现 C 11 禁止字符串的写时复制 COW 据我了解 如果应用了 COW 那么p仍然是一个有效的指针并且r以下命令后的有效参考 std string s abc std st
  • Project Euler #8,我不明白我哪里出了问题[关闭]

    Closed 这个问题是无法重现或由拼写错误引起 help closed questions 目前不接受答案 我正在做项目欧拉第八题 https projecteuler net problem 8 其中我得到了这个大得离谱的数字 7316
  • Linux mremap 不释放旧映射?

    我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围 而无需实际复制数据 范围很大 延迟很重要 mremap 可以做到这一点 但问题是它也会删除旧的映射 由于我需要在多线程环境中执行此操作 因此我需要旧映射能够同时使用 因此稍后当
  • CUDA 8 编译错误 -std=gnu++11

    我正在尝试转换一些代码以使用 CUDA 并且我认为我遇到了兼容性问题 我们使用CMake 这些是我使用的 gcc 和 CUDA 版本 gcc version gcc Ubuntu 5 4 0 6ubuntu1 16 04 5 5 4 0 2
  • 在 C#.NET 中安全删除文件

    在我正在做的一个项目中 我想为用户提供 安全 删除文件的选项 例如 用随机位或 0 覆盖它 在 C NET 中是否有一种简单的方法可以做到这一点 效果如何 你可以调用系统内部删除 http technet microsoft com en
  • 如何调试 .NET 运行时中的内部错误?

    我正在尝试调试一些处理大文件的工作 代码本身works 但 NET 运行时本身会报告零星错误 对于上下文 这里的处理是一个 1 5GB 文件 仅加载到内存中一次 在循环中处理和释放 故意尝试重现此否则不可预测的错误 我的测试片段基本上是 t
  • 为什么以下 C 程序会出现总线错误?

    我认为这是第一个失败的 strtok 调用 好久没写C了 有点不知所措 非常感谢 include
  • 使用 using 声明时,非限定名称查找如何工作?

    根据 C 标准 这是格式错误还是格式良好 namespace M struct i namespace N static int i 1 using M i using N i int main sizeof i Clang 拒绝它 GCC
  • INotifyPropertyChanged 和 propertyName

    我一直不确定它的含义propertyName实施时INotifyPropertyChanged 所以一般来说你实现INotifyPropertyChanged as public class Data INotifyPropertyChan
  • 为什么匹配模板类上的部分类模板特化与没有模板匹配的另一个部分特化不明确?

    这个问题可能很难用标题中的句子来描述 但这里有一个最小的例子 include
  • 使用未分配的局部变量

    我遇到了一个错误 尽管声明了变量 failturetext 和 userName 错误仍然出现 谁能帮帮我吗 Use of Unassigned local variable FailureText Use of Unassigned lo

随机推荐

  • 压力测试-JMeter的多种形式参数化

    在使用JMeter做压力测试 接口测试时 面对数据量比较大的情况下一个一个的去设置肯定会非常影响效率 所以参数化的方式必不可少 本文分享JMeter常用的几种参数化的形式 准备工作 创建基础API框架 新建测试计划 创建线程组 创建HTTP
  • blender中常用快捷键的总结

    学习过程中遇到较为重要的blender快捷键 总结 在移动 G 缩放 S 旋转 的时候 按住 XYZ 代表限定方向 alt R G S代表重置 扩展 R在进行旋转的时候按鼠标滚轮会定位到最近的坐标轴 进行校对 shift A 创建一个对象
  • 无线通信(LoRa和zigbee,补充WIFI)

    原文 1 LoRa是物联网应用中的无线技术有多种 可组成局域网或广域网 2 ZigBee是基于IEEE802 15 4标准的低功耗局域网协议 1 简介 LoRa 是LPWAN通信技术中的一种 是美国Semtech公司采用和推广的一种基于扩频
  • vue 数据更新,视图未更新,原因,解决方法

    复现问题
  • geth web3提供的接口

    admin datadir ethcluster 779977 data 01 nodeInfo enode enode ca624860483a9f749676491bbf5b11cc7ded0a89f5c9f522767ebea0195
  • docker概念、安装与卸载

    第一章 docker概念 Docker 是一个开源的应用容器引擎 Docker 诞生于2013年初 基于 Go 语言实现 dotCloud 公司出品 后改名为 Docker Inc Docker 可以让开发者打包他们的应用以及依赖包到一个轻
  • 实习记录(1)——数据标注

    使用数据标注工具labelme对图像进行标注 一 labelme的安装 首先需要python环境 激活anaconda环境后可以直接输入以下指令 pip install labelme 注意 建议以管理员权限打开dos 不然可能安装报错 二
  • UniApp 组件内修改组件内的组件的样式,穿透组件中的组件样式

    UniApp 组件内修改组件内的组件的样式 穿透组件中的组件样式 main vue 这是一个页面 这是 a 组件 这是 b 组件 页面可以修改页面引入的组件样式 直接使用 deep 例如 main页面 修改 a组件的样式
  • python正则表达式匹配ip地址

    首先要引入re模块 import re re search r 01 0 1 d 0 1 d 2 0 4 d 25 0 5 3 01 0 1 d 0 1 d 2 0 4 d 25 0 5 1 2 3 4 真实实战 import urllib
  • 问题点-28-Gradle4.9升级到Gradle7.2产生的问题

    由于业务需要 需要将gradle版本从4 9升级到7 2 于是出现一系列问题 汇总如下 idea版本 不要使用2019版本 建议使用2021版本 不然会抛错 项目环境需要有gradle对应的版本 maven gt maven publish
  • es_MySQL、HBase、ElasticSearch三者对比详解

    1 概念介绍 MySQL 关系型数据库 主要面向OLTP OLTP 也叫联机事务处理 Online Transaction Processing 支持事务 支持二级索引 支持sql 支持主从 Group Replication MGR 是一
  • SQLServer数据库数据备份的几种方法

    采用MS SQLServer数据库 在开发的过程中 需要对数据库的结构及数据进行备份 以便在另一个系统中进行安装和恢复 一般采用以下四种方法来处理 1 对数据库生成SQL脚本 恢复时通过查询分析器执行脚本 2 对数据库执行备份操作 恢复时先
  • 明年,HarmonyOS不再兼容Android应用!

    2023年华为开发者大会 不知道各位老铁们是否观看了 一个震撼的消息就是 首次公开了HarmonyOS NEXT的概念 简而言之就是 这是一款专为开发者打造的预览版操作系统 旨在提供 纯正鸿蒙操作系统 的体验 与之前的版本不同 Harmon
  • SpringBoot系列FastJson篇之@JsonField

    在实际运用中 JsonField主要有三个用处 1 修改和json字符串的字段映射 name 2 格式化数据 format 3 过滤掉不需要序列化的字段 serialize 首先声明在低版本中区分注解加在setter和getter方法 而高
  • Java的运算符

    目录 一 什么是运算符 二 算术运算符 1 基本四则运算符 加减乘除模 2 增量运算符 3 自增 自减运算符 三 关系运算符 四 逻辑运算符 重点 1 逻辑与 2 逻辑或 3 逻辑非 4 短路求值 五 位运算符 1 按位与 2 按位或 3
  • python定义函数求和_Python定义函数实现累计求和操作

    一 使用三种方法实现0 n累加求和 定义函数分别使用while循环 for循环 递归函数实现对0 n的累加求和 1 使用while循环 定义一个累加求和函数sum1 n 函数代码如下 2 使用 for循环 定义一个累加求和函数sum2 n
  • c++虚函数详解

    1 虚函数的简介 由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数 所以被成为 虚 函数 用父类型别的指针指向其子类的实例 然后通过父类的指针调用实际子类的成员函数 这种技术可以让父类的指针有 多种形态 这是一种泛型技术
  • 优先队列的实践

    一 使用背景 对于前 k 大或前 k 小这类问题 有一个通用的解法 优先队列 优先队列可以在 O log n 的时间内完成插入或删除元素的操作 其中 n 为优先队列的大小 并可以 O 1 地查询优先队列顶端元素 二 前K个高频单词 给定一个
  • 巡检过程中有哪些注意事项?智能巡检了解一下

    智能巡检系统是现场过程管理的生产力革命 由人工记录蝶化为掌上电脑运作 适用于设备运行值班记录 仓库 资产管理 设备巡检保养 安全巡更 机房值守 基站维护等一切重复性的工作管理 安全巡检的目的在于识别信息系统存在的安全脆弱性 分析信息系统存在
  • C++:构造/析构/赋值运算(Effective C++)

    文章目录 写在前面 05 了解C 默默编写并调用哪些函数 06 若不想使用编译器自动生成的函数 就该明确拒绝 10 令operator 返回一个reference to this 11 在operator 中处理 自我赋值 12 复制对象时