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

当定义一个类时,我们显式地或隐式地指定在此类型的对象拷贝、移动、赋值和销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作,包括:拷贝构造函数(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. 右值引用和成员函数

右值和左值引用成员函数

重载和引用函数

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

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

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

相关文章

web自动化的断言和日志封装

断言 UI自动化常见的断言条件包括: 通过当前页面的URL地址通过当前页面的标题通过当前页面的提示文本信息通过当前页面的某些元素变化/显示 一句话总结:通过肉眼观察页面的变化检查。 【用代码模仿人的识别页面】 一般断言写一条就够了,如…

Visual Studio 智能代码插件:CodeGeeX

前言 在软件开发领域,高效的编程助手一直是提升开发者效率和质量的关键。 随着人工智能技术的不断发展,智能编程助手逐渐成为开发者们不可或缺的工具。其中,CodeGeeX作为一款专为Visual Studio设计的免费智能编程助手,凭借其强大…

goimghdr,一个有趣的 Python 库!

更多Python学习内容:ipengtao.com 大家好,今天为大家分享一个有趣的 Python 库 - goimghdr。 Github地址:https://github.com/corona10/goimghdr 在图像处理和分析过程中,识别图像文件的类型是一个常见的需求。Python自带的imghdr…

AT32F415 使用embedded studio笔记

由于Embedded Studio还没有支持雅特力的支持包,因此通过MDK的工程无法直接导入到embedded studio中。需要自己新建一个工程。 新建的时候是可以选择雅特力的单片机的。 工程新建好后,默认会包含3个文件。如下图所示。其中两个汇编文件的作用是之前MDK的…

出现 Transaction rolled back because it has been marked as rollback-only 解决方法

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 用户反馈的Bug如下所示: Transaction rolled back because it has been marked as rollback-only截图如下: 浏览器终端同样显示: 2. 原理分析 错误表明,在事务的生命周期内,遇到了某个异常或条件,导致该事务被标记…

Vue3学习使用axios和qs进行POST请求和响应处理

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 一、前言1.准备工作2.发送POST请求3.处理响应数据4.总结 一、前言 在前端开发中,经常需要与后端进行数据交互,其中包括发送POST请求并处理响…

2024年中国金融行业网络安全市场全景图

网络安全一直是国家安全的核心组成部分,特别是在金融行业,金融机构拥有大量的敏感数据,包括个人信息、交易记录、财务报告等,这些数据的安全直接关系到消费者的利益和金融市场的稳定,因此金融行业在网络安全建设领域一…

【机器学习】利用机器学习优化陆军战术决策与战场态势感知

🔒文章目录: 💥1.引言 🛴2.机器学习在陆军战术决策中的应用 🛣️2.1数据收集与预处理 🌄2.2模型构建与训练: 🌅2.3实时决策支持: 🌅2.4代码实现 &…

Genzai:一款针对物联网安全的多功能实用性工具套件

关于Genzai Genzai是一款针对物联网安全的多功能实用性工具套件,该工具旨在识别与物联网相关的仪表盘,并扫描它们以查找默认密码和安全问题,广大研究人员可以使用该工具来检测和提升物联网设备的安全性。 Genzai支持用户以输入的形式提供一个…

电子围栏(地理围栏)设计逻辑

做完整的项目时需要考虑安全问题,判断车辆在不该出现的位置出现时自动刹车。 只能说可以有吧。 地理围栏的概念 自动驾驶地理围栏是指在自动驾驶系统中定义的一种虚拟边界,用于限制车辆的运行范围。地理围栏可以通过全球定位系统(GPS&#…

nginx设置一个TCP代理(用于RDP连接)

在 Nginx 中,stream 上下文必须在 http 上下文之外,并且只能位于顶级配置文件中或包含在主配置文件中的单独文件中。 Nginx 的配置文件通常包含一个 http 块,用于处理 HTTP 和 HTTPS 请求,但是 stream 块是独立的,并且…

Acrobat Pro DC 2024 Mac软件安装包下载PDF2024 Mac安装教程

安装 步骤 1,双击打开下载好的安装包。 2,选择acrobat dc installer.pkg双击启动安装程序。 3,点击继续。 4,点击继续。 5,点击继续。 6,点击安装。 7,输入电脑密码。 8,软件安装中…

香橙派 AIpro开发板初上手

一、香橙派 AIpro开箱 最近拿到了香橙派 AIpro(OrangePi AIpro),下面就是里面的板子和相关的配件。包含主板、散热组件、电源适配器、双C口电源线、32GB SD卡。我手上的这个是8G LPDDR4X运存的版本。 OrangePi AIpro开发板是一款由香橙派与华…

JAVA学习·String类的常用方法

String 类及其创建 String 类的创建 String 类是 Java 内置的一个类,其完全限定类名是java.lang.String。想要创建一个字符串有多重方式,比如创建字符串"Hello": String s1 "Hello"; // 字面量创建 String s2 new St…

linux部署rustdesk

1.拉取RustDesk镜像 sudo docker image pull rustdesk/rustdesk-server2.启动hbbs服务 sudo docker run --name hbbs -p 21115:21115 -p 21116:21116 -p 21116:21116/udp -p 21118:21118 -v pwd:/root -td --nethost rustdesk/rustdesk-server hbbs3.启动hbbr服务 sudo dock…

如何从Android恢复已删除的文件?3 种有效的方式

有时我们可能会错误地删除Android设备上的重要文件。更疯狂的是,Android手机上的文件在一夜之间消失了,我们不知道为什么。我们感到非常遗憾和恼火,但不知道。但是,此时学习如何从Android手机恢复已删除的文件为时已晚&#xff0c…

Mesa Gallium框架入门初探

Mesa Gallium框架入门初探 MESA Gallium框架 MESA源码里面有2套架构,现在驱动主要基于Gallium架构。 这里我们重点来看看Gallium架构: 经典架构 Gallium架构 Gallium展开 Gallium中主要包含下面几块: Auxiliary模块:一些公共函数或者辅助…

Word如何绘制三线表及设置磅值

插入表格, 开始 边框 边框和低温 设置磅值 先全部设置为无边框 上边 1.5 0.5 以上内容未完善,请等待作者更新

内网权限提升

打点进入内网中,权限一般为 web 服务的权限 1、什么是提权 一般一来说是低权限用户去申请高权限用户,可以是(配置不当、溢出类漏洞(历史漏洞)、本地漏洞) 2、常见用户分类 windows: 本地登录…

使用 Orange Pi AIpro开发板基于 YOLOv8 进行USB 摄像头实时目标检测

简介 官网:Orange-Pi-AIpro 算力指标与概念 TOPS是每秒数万亿或万亿次操作。它主要是衡量可实现的最大吞吐量,而不是实际吞吐量的衡量标准。大多数操作是 MAC(multiply/accumulates),因此: TOPS &#xf…