C++ Primer 第五版 第十三章 拷贝控制

news/2024/7/14 18:54:41/文章来源:https://blog.csdn.net/weixin_44500921/article/details/139006868

当定义一个类时,我们显式地或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数(copy constructor)、拷贝赋值运算符(copy-assignment operator)、移动构造函数(move constructor)、移动赋值运算符(move-assignment operator)和析构函数(destructor)。我们称这些操作为拷贝控制操作(copy control)。

一、拷贝、赋值与销毁
1. 拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。

拷贝构造函数的第一个参数必须是一个引用类型。虽然我们可以定义一个接受非const引用的拷贝构造函数,但此参数几乎总是一个const的引用。

(如果其参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们有需要调用拷贝构造函数,如此无限循环。)

合成拷贝构造函数

编译器会合成一个拷贝构造函数。合成的拷贝构造函数会将其参数的成员逐个拷贝到正在创建的对象中。编译器从给定对象中依次将每个非static成员拷贝到正在创建的对象中。

每个成员的类型决定了它如何拷贝:对类类型的成员,会使用其拷贝的构造函数来拷贝;内置类型的成员则直接拷贝。虽然我们不能直接拷贝一个数组,但合成拷贝构造函数会逐元素地拷贝一个数组类型的成员。

拷贝初始化

拷贝初始化通常使用拷贝构造函数来完成。如果一个类有一个移动构造函数,则拷贝初始化有时会使用移动构造函数而非拷贝构造函数来完成。

编译器可以绕过拷贝构造函数

2. 拷贝赋值运算符
重载赋值运算符

重载运算符本质上是函数,其名字由operator关键字后接要定义的运算符的符号组成。因此,赋值运算符就是一个名为operator=的函数。运算符函数也有一个返回类型和一个参数列表。

拷贝赋值运算符本身是一个重载的赋值运算符,定义为类的成员函数,左侧运算对象绑定到蕴含的this参数,而右侧的运算对象是所属类类型的,作为函数的参数。函数返回指向其左侧运算对象的引用。

当对类对象进行赋值时,会使用拷贝赋值运算符。

如果类未定义自己的拷贝赋值运算符,编译器会为它合成一个。通常情况下,合成的拷贝赋值运算符会将右侧对象的非static成员逐个赋予左侧对象的对应成员,这些赋值操作是由类型的拷贝赋值运算符完成的。

3. 析构函数

析构函数释放对象使用的资源,并销毁对象的非static数据成员。

析构函数是类的一个成员函数,名字由波浪号接类名构成。它没有返回值,也不接受参数,因此也不能被重载。对于一个给定类,只会有唯一一个析构函数。

在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照它们在类中出现的顺序进行初始化。在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。

隐式合成的析构函数体为空,但这并不意味着它什么也不做,当空函数体执行完后,非静态数据成员会被逐个销毁。成员是在析构函数体之外隐含的析构阶段中被销毁的。析构部分是隐式的。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。

销毁一个内置指针类型的成员不会delete它所指向的对象。与普通指针不同,智能指针是类类型,所以具有析构函数,智能指针成员在析构阶段会被自动销毁。

4. 三/五法则

有三个基本操作可以控制类的拷贝操作:拷贝构造函数、拷贝赋值运算符和析构函数。在新标准下,一个类还可以定义一个移动构造函数和一个移动赋值运算符。

需要析构函数的类也需要拷贝和赋值操作
需要拷贝操作的类也需要赋值操作,反之亦然
5.  使用=default

我们可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本。

6. 阻止拷贝

定义删除的函数

在新标准下,我们可以通过将拷贝构造函数和拷贝赋值运算符定义为删除的函数(deleted function)来阻止拷贝。删除的函数是这样一种函数:我们虽然声明了它们,但不能以任何方式使用它们。在函数的参数列表后面加上=deleted来指出我们希望将它定义为删除的。

=delete通知编译器(以及我们代码的读者),我们不希望定义这些成员。

与=default不同,=delete必须出现在函数第一次声明的时候。我们可以对任何函数指定=delete(我们只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default)。

析构函数不能是删除的成员
合成的拷贝控制成员可能是删除的

对某些类来说,编译器将这些合成的成员定义为删除的函数:

二、 拷贝控制和资源管理

通常,管理类外资源的类必须定义拷贝控制成员。这种类需要通过析构函数来释放对象所分配的资源。一旦一个类需要析构函数,那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。

1. 行为像值的类

为了提供类值的行为,对于类管理的资源,每个对象都应该拥有一份自己的拷贝。

赋值运算符通常祝贺了析构函数和构造函数的操作。

2. 定义行为像指针的类

对于行为类似指针的类,我们需要为其定义拷贝构造函数和拷贝赋值运算符,来拷贝指针成员本身而不是它指向的string。我们的类仍需要自己的析构函数来释放接受string参数的构造函数分配的内存。在本例中,只有当最后一个指向string的HasPtr销毁时,析构函数才可以释放string。

引用计数

定义一个使用引用计数的类

三、 动态内存管理类
移动构造函数和std::move

一些标准库类,包括string,都定义了所谓的“移动构造函数”。移动构造函数通常是将资源从给定对象“移动”到正在创建的对象。标准库保证“移后源(moved-from)”string仍然保持一个有效的、可析构的状态。

move标准库函数定义在utility头文件中。①当realloctae在新内存中构造string时,它必须调用move来表示希望使用string的移动构造函数,如果它漏掉了move调用,将会使用string的拷贝构造函数。②我们通常不为move提供一个using声明。当我们使用move时,直接调用std::move而不是move。

四、对象移动

1. 右值引用

所谓右值引用就是必须绑定到右值的引用。我们通过&&而不是&来获得右值引用。

右值引用只能绑定到一个将要销毁的对象。因此,我们可以自由地将一个右值引用地资源“移动”到另一个对象中。

一般而言,一个左值表达式表示地是一个对象的身份,而一个右值表达式表示的是对象的值。

左值持久;右值短暂

左值有持久的状态,而右值要么是字面常量。要么是在表达式求值过程中创建的临时对象。

变量是左值

标准库move函数

move函数返回给定对象的右值引用。

调用move意味着:除了对rr1赋值或销毁它外,我们将不再使用它。

返回左值的表达式包括:返回左值引用的函数及赋值、下标、解引用和前置递增/递减运算符;

返回右值的包括:返回非引用类型的函数及算数、关系、位以及后置递增/递减运算符。(我们可以将一个const的左值引用或者一个右值引用绑定到这类表达式上。)

2. 移动构造函数和移动赋值运算符

移动构造函数的第一个参数是该类类型的一个右值引用,任何额外的参数都必须有默认实参。

除了完成资源移动,移动构造函数还必须确保移后源对象处于这样一个状态——销毁它是无害的。特别是,一旦资源完成移动,源对象必须不再指向被移动的资源——这些资源的所有权已经归属新创建的对象。

与拷贝构造函数不同,移动构造函数不分配任何新内存:它接管给定的StrVec中的内存。最终,移后源对象会被销毁,意味着将在其上运行析构函数。

移动操作通常不会抛出任何异常。除非标准库知道我们的移动构造函数不会抛出异常,否则它会认为移动我们的类对象时可能会抛出异常,并且为了处理这种可能性而做一些额外的工作。

一种通知标准库的方法是在我们的构造函数中指明noexpect。我们在一个函数的参数列表后指定noexpect。

我们必须在类头文件的声明中和定义中(如果定义在类外的话)都指定noexpect。

移动赋值运算符

移动赋值运算符执行与析构函数和移动构造函数相同的工作。与移动构造函数一样,如果我们的移动赋值运算符不抛出任何异常,我们就应该将它标记为noexpect。

移后源对象必须可析构

从一个对象移动数据并不会销毁此对象,但有时在移动操作完成后,源对象会被销毁。因此,当我们编写一个移动操作时,必须确保移后源对象进入一个可析构的状态(析构安全的状态)。

合成的移动操作

只有当一个类没有定于任何自己版本的拷贝控制成员(拷贝构造函数、拷贝赋值运算符或析构函数),且类的每个非static数据成员都可以移动时,编译器才会为它合成移动构造函数或移动赋值运算符。编译器可以移动内置类型的成员。如果一个成员时类类型。且该类有对应的移动操作,编译器也能移动这个成员。

移动右值,拷贝左值

但如果没有移动构造函数,右值也被拷贝。

移动迭代器

移动迭代器的解引用运算符生成一个右值引用。

我们通过调用标准库的make_move_iterator函数将一个普通迭代器转换为一个移动迭代器。此函数接受一个迭代器参数,返回一个移动迭代器。

移动迭代器支持正常的迭代器操作。

3. 右值引用和成员函数

右值和左值引用成员函数

重载和引用函数

引用限定符也可以区分重载版本。

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

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

相关文章

9、C#【进阶】特性

特性 文章目录 1、特性概念2、自定义特性 Attribute3、特性的使用4、限制自定义特性的使用范围5、系统自带特性1、过时特性2、调用者信息特性3、条件编译特性4、外部dll包函数特性 1、特性概念 特性是一种允许我们向程序的程序集添加元数据的语言结构 它是用于保存程序机构信息…

全网首发!精选32个最新计算机毕设实战项目(附源码),拿走就用!

Hi 大家好,马上毕业季又要开始了,陆陆续续又要准备毕业设计了,有些学生轻而易举就搞定了,有些学生压根没有思路怎么做,可能是因为技术问题,也可能是因为经验问题。 计算机毕业相关的设计最近几年类型比较多…

Docker 开启 SSL 验证

最近看 OJ 项目的远程开发阶段,然后踩坑踩了 2 天😂 Docker 版本:在 CentOS 安装 sudo yum install docker-ce-20.10.9 docker-ce-cli-20.10.9 containerd.io Client: Docker Engine - CommunityVersion: 20.10.9API version: …

如何远程访问Redis?

远程访问Redis是一种常见的需求,特别是在分布式系统或跨地域网络中。通过远程访问,我们可以轻松地对远程的Redis数据库进行操作和管理。 天联保障数据安全 对于远程访问Redis的安全性问题,我们可以借助天联来保障数据的安全。天联是一种基于…

Pytorch创建张量

文章目录 1.torch.from_numpy()2. torch.zeros()3. torch.ones()4. torch.arange()5. torch.linspace()6. torch.logspace()7. torch.eye()8. torch.empty()9. torch.full()10. torch.complex()10. torch.rand()10. torch.randint()11. torch.randn12. torch.normal()13. torch…

概率分布函数与误差函数的关系

正态函数(高斯分布) 对其求[b,x]区间的积分 标准误差函数 以下两个方程相等(a,b取值任意) 两个函数重合 可知正态函数 f(t) 在[b,x]的区间上积分等于 引用desmos计算器:Desmos | Lets learn together.

9.4 Go语言入门(运算符)

Go语言入门(运算符) 目录三、运算符1. 算术运算符2. 关系运算符3. 逻辑运算符4. 位运算符5. 赋值运算符6. 其他运算符7. 运算符优先级 目录 Go 语言(Golang)是一种静态类型、编译型语言,由 Google 开发,专注…

他用AI,抄袭了我的AI作品

《大话西游》里面有一句经典台词:每个人都有一个妈,但是“你妈就一定是你妈吗?” 用AI创作的艺术作品,也走进类似的困境:如何证明你用AI生成的作品,就是你的作品? 近日,腾讯科技独…

编程-辅助工具-Git下载

文章目录 1、前言2、Git官网地址3、迅雷下载 1、前言 采用Git能下载github上的代码,其下载是采用官网下载的,但是下载速度比较慢,网上也推荐了镜像的方式,但是有些链接失效了,突然有一天想起用迅雷是不是合适&#xf…

【Sql Server】随机查询一条表记录,并重重温回顾下存储过程的封装和使用

大家好,我是全栈小5,欢迎来到《小5讲堂》。 这是《Sql Server》系列文章,每篇文章将以博主理解的角度展开讲解。 温馨提示:博主能力有限,理解水平有限,若有不对之处望指正! 目录 前言随机查询语…

AI办公自动化:用kimi批量将word文档部分文件名保存到Excel中

文件夹中有很多个word文档,现在只要英文部分的文件名,保存到一个Excel文件中。 可以在kimi中输入提示词: 你是一个Python编程专家,要完成一个编写Python脚本的任务,具体步骤如下: 打开文件夹:…

Linux IO模型深度解析与实战应用

linux的5种IO模型 一、这里IO是什么 操作系统设有用户态与内核态,确保系统安全。应用程序默认在用户态运行,而执行如IO操作等底层任务时,需切换至内核态以高效执行。 服务器从网络接收的大致流程如下: 1、数据通过计算机网络来到了网卡 2、把网卡的数据读取到 socket 缓…

将 KNX 接入 Home Assistant 之一 准备硬件

不久前有人小伙伴买了usb转knx ,详情请看 一个 usb 转 knx 的模块 然后想通过这个设备接入 Home Assistant。 后来了解了一下 Home Assistant 并不直接支持 usb转KNX的接入,需要通过KNXD插件转接才行。 然而尝试了很多次叶没有成功,使用西门子的usb接口以…

Android面试题之Kotlin常见集合操作技巧

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 list 创建和修改 不可变list,listOf var list listOf("a","d","f") println(list.getOrElse(3){"Unkn…

关于高性能滤波器和普通型滤波器的区别说明

高性能滤波器和普通型滤波器在性能和滤波效果上存在显著差异。以三安培为代表分析高性能滤波器和普通型滤波器的区别: 从上图曲线可看出: 1.高性能滤波器和普通型滤波器的滤波范围不同。普通型滤波器有效滤波范围为 150KHz~30MHz,而高性能滤…

蓝牙Mesh模块多跳大数据量高带宽传输数据方法

随着物联网技术的飞速发展,越来越多的设备需要实现互联互通。蓝牙Mesh网络作为一种低功耗、高覆盖、易于部署的无线通信技术,已经成为物联网领域中的关键技术之一。在蓝牙Mesh网络中,节点之间可以通过多个跳数进行通信,从而实现大…

剪画小程序:”霸屏各大平台“的黏土滤镜是怎么制作的呢?

最近,网上出现大量“黏土”风格的人物照片。尤其是在社交平台,这类型的分享数量急剧上升。 这是马斯克开车的样子 还有这张是周杰伦七里香的专辑图片 一张照片,十几秒钟,就能还原出你在黏土世界的样子。 以上这些照片是用-【剪画…

解决SpringBoot中插入汉字变成?(一秒解决)

在这里url后面加一行配置即可&useUnicodetrue&characterEncodingUTF-8即可 解释 spring.datasource.url: 这里包含了数据库的URL,以及额外的参数如useUnicodetrue用于启用Unicode字符集支持,characterEncodingUTF-8用于指定字符编码为UTF-8&…

idea2024 nacos中文报错

idea2024 nacos中文报错 报错提示为:Input length 1 报错原因:项目启动编码与nacos编码不一致。 处理方式 添加启动参数utf8修改项目编码格式为utf8 修改idea.vmoptions Help -> Edit Custom 添加一行:-Dfile.encodingUTF-8

Django与微服务架构:构建可扩展的Web应用

title: Django与微服务架构:构建可扩展的Web应用 date: 2024/5/21 20:15:19 updated: 2024/5/21 20:15:19 categories: 后端开发 tags: 微服务Django负载均衡RESTfulAPI网关容器化监控安全 前言 在当今快速发展的软件开发领域,微服务架构已经成为构建…