13-1 c++拷贝控制:拷贝赋值与销毁

定义一个类时,我们必须对它进行拷贝控制,即控制该类在进行拷贝、赋值、移动和销毁时要进行哪些操作
一个类通过五个特殊的成员函数进行拷贝控制
- 拷贝构造函数
- 拷贝赋值函数
- 移动构造函数
- 移动赋值函数
- 析构函数
拷贝构造和移动构造函数:用同类型初始化对象时该做什么
拷贝和赋值运算符:将一个对象赋予同类型对象时该做什么
析构函数:对象销毁时该做什么
在定义任何C++类时,拷贝控制操作都是必要部分。对初学C+的程序员来说,必须定义对象拷贝、移动、赋值或销毁时做什么,这常常令他们感到困惑。这种困扰很复杂,因为如果我们不显式定义这些操作,编译器也会为我们定义,但编译器定义的版本的行为可能并非我们所想。
13.1.1 拷贝构造函数
第一个参数是自身的引用,且任何其他参数都有默认值
class Foo{
Foo(); //默认构造函数
Foo(const Foo&); //拷贝构造函数
};
合成拷贝构造函数
如果我们没有定义拷贝函数,编译器会自动生成
和合成默认构造函数不同,即使我们定义了其他构造函数,编译器仍会生成拷贝构造函数
成员类型决定了如何拷贝:
- 内置类型:直接拷贝
- 类类型:调用该类类型的拷贝构造函数
例子:

注意:合成的拷贝构造函数都是接受const引用
拷贝初始化

- 直接初始化:普通的函数匹配
- 拷贝初始化:将等号右侧的对象通过拷贝构造函数拷贝正在创建的对象中去,如果需要会进行类型转换
注:拷贝初始化有时会调用移动构造函数
拷贝初始化合适发生?
- 在使用=定义变量时发生
- 将一个对象作为实参传递给一个非引用类型的形参从一个返回类型为非引用类型的函数返回一个对象
- 用花括号列表初始化一个数组中的元素或一个聚合类中的成员(参见7.5.5节,第266页)
某些类类型还会对它们所分配的对象使用拷贝初始化。例如,当我们初始化标准库容器或是调用其insert或push成员(参见9.3.1节,第306页)时,容器会对其元素进行拷贝初始化。与之相对,用emplace成员创建的元素都进行直接初始化(参见9.3.1节,第308页)。
参数和返回值
在函数调用过程中,具有非引用类型的参数要进行拷贝初始化。类似的,当一个函数具有非引用的返回类型时,返回值会被用来初始化调用方的结果。
拷贝构造函数被用来初始化非引用类类型参数,这一特性解释了为什么拷贝构造函数自己的参数必须是引用类型。
如果其参数不是引用类型,则调用永远也不会成功——为了调用拷贝构造函数,我们必须拷贝它的实参,但为了拷贝实参,我们又需要调用拷贝构造函数,如此无限循环。
拷贝初始化的限制
如果拷贝构造函数是explicit的,无法发生隐式类型转换,那么拷贝初始化和直接初始化就没有什么区别了

编译器可以绕过拷贝构造函数
在进行拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝/移动构造函数,直接创建对象。即编译器允许将下面的代码
string null_book = "999"; //拷贝初始化
改写为:
string null_book("999"); //编译器略过了拷贝构造函数
但是,即使编译器略过了拷贝/移动构造函数,但在这个程序点上,拷贝/移动构造函数必须是存在且可访问的(例如,不能是private的)。
13.1.2 拷贝赋值运算符
与类控制器其兑现对象的初始化一样,类也可以控制其赋值
Sale_data trans, accum;
trans = accum; //使用Sale_data的拷贝运算符
如果类没有定义拷贝运算符,编译器会为它合成一个
重载赋值运算符
运算符的本质是函数,重载运算符本质是函数的重载
运算符是一个成员函数,对于一个二元运算符(如赋值运算符)
- 参数:左侧对象绑定到隐式的this,右侧对象显示传递
- 返回:对象的引用
//拷贝运算符接受一个与所在类同类型的对象
class Foo{
public:
Foo& operator=(const Foo&); //赋值运算符
};
赋值运算符一般返回左侧对象的引用
合成拷贝赋值运算符
与处理拷贝构造函数一样,如果一个类未定义自己的拷贝赋值运算符,编译器会为它生成一个合成拷贝赋值运算符(synthesized copy-assignment operator)。
行为:
将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,这一工作是通过成员类型的拷贝赋值运算符来完成的。对于数组类型的成员,逐个赋值数组元素。
合成拷贝赋值运算符返回一个指向其左侧运算对象的引用。

13.1.3 析构函数
析构函数执行与构造函数相反的操作:构造函数初始化对象的非static数据成员
析构函数释放对象使用的资源,并销毁对象的非static数据成员。
形式:波浪线+类名,不接受参数
~Foo();//Foo类的析构函数
由于不接受参数,所以无法重载,每个类只能有一个析构函数
析构函数完成什么工作
构造函数先初始化,后执行函数体
析构函数先执行函数体,再销毁对象释放资源
析构过程的注意点
销毁成员是按初始化顺序的逆序销毁
析构部分是隐式的,无需程序员编写
成员具体如何销毁取决于**成员类型*8
内置类型没有析构函数,什么也不做
智能指针有析构函数,会被销毁;
普通指针没有,所以new动态分配内存时要手动delete
类类型执行自己的析构函数
什么时候调用析构函数
- 变量在离开其作用域时被销毁。
- 当一个对象被销毁时,其成员被销毁。
- 容器(无论是标准库容器还是数组)被销毁时,其元素被销毁。
- 对于动态分配的对象,当对指向它的指针应用delete运算符时被销毁
- 对于临时对象,当创建它的完整表达式结束时被销毁。

当一个指针或引用离开作用域时,指针变量和引用变量被销毁,但是它们所指向的对象没有被销毁,指向的对象的析构函数不会执行
合成析构函数
一个类未定义自己的析构函数时,编译器会为它自动合成

在析构函数的函数体执行完毕后,对象被销毁
注意:认识到析构函数体自身并不直接销毁成员是非常重要的。成员是在析构函数体之后隐含的析构阶段中被销毁的。在整个对象销毁过程中,析构函数体是作为成员销毁步骤之外的另一部分而进行的,一般用来打印些提示信息。
13.1.4 三/五法则
三个基本操作可以控制类的拷贝操作:拷贝构造、拷贝赋值和析构
在新标准下,还可以定义移动构造函数和移动运算符
C++语言并不要求我们定义所有这些操作:可以只定义其中一个或两个,而不必定义所有。但是,这些操作通常应该被看作一个整体。通常,只需要其中一个操作,而不需要定义所有操作的情况是很少见的。
下面是两条一般性的规则
- 需要析构函数的类也需要拷贝和赋值
- 需要拷贝的类需要赋值,反之亦然
13.1.5 使用=default
用=default来显式地使用编译器的合成版本

注意参数都是const引用传递
13.1.6 阻止拷贝
定义删除的函数
删除的函数=delete:
声明了,但不能使用。告诉编译器我们不需要定义此函数
struct NoCopy{
NoCopy() = default; //使用合成的默认构造函数
NoCopy(const NoCopy&) = delete; //阻止拷贝
NoCopy& operator=(const NoCopy&) = delete; //阻止赋值
~NoCopy(); //使用合成的析构函数
//其他成员
};
与=default的另一个不同之处是,我们可以对任何函数指定=delete(我们只能对编译器可以合成的默认构造函数或拷贝控制成员使用=default)。虽然删除函数的主要用途是禁止拷贝控制成员,但当我们希望引导函数匹配过程时,删除函数有时也是有用的。
析构函数不能是删除的成员
对于析构函数删除的类而言,我们无法销毁对象,所以
- 无法定义该类型的对象
- 可以动态分配该对象,但无法delete

对于析构函数已删除的类型,不能定义该类型的变量或释放指向该类型动态分配对象的指针。
合成的拷贝控制成员可能是删除的
如果一个类有数据成员不能默认构造、拷贝、复制或销毁则对应的成员函数将被定义为删除的:
- 如果类的某个成员的析构函数是删除的或不可访问的(例如,是 private 的),则类的合成析构函数被定义为删除的。
- 如果类的某个成员的拷贝构造函数是删除的或不可访问的,则类的合成拷贝构造函数被定义为删除的。如果类的某个成员的析构函数是删除的或不可访问的,则类合成的拷贝构造函数也被定义为删除的。
- 如果类的某个成员的拷贝赋值运算符是删除的或不可访问的,或是类有一个const的或引用成员,则类的合成拷贝赋值运算符被定义为删除的。
- 如果类的某个成员的析构函数是删除的或不可访问的,或是类有一个引用成员,它没有类内初始化器(参见2.6.1节,第65页),或是类有一个const成员,它没有类内初始化器且其类型未显式定义默认构造函数,则该类的默认构造函数被定义为删除的。
对于有const成员的类
- 编译器不会合成默认构造函数
- 不能使用拷贝和赋值运算(毕竟,合成的赋值运算会赋值给所有成员,而赋值给const成员是不被允许的)
对于有引用成员的类:合成拷贝赋值运算符被定义为删除的。
虽然我们可以将一个新值赋予一个引用成员,但这样做改变的是引用指向的对象的值,而不是引用本身。如果为这样的类合成拷贝赋值运算符,则赋值后,左侧运算对象仍然指向与赋值前一样的对象,而不会与右侧运算对象指向相同的对象。由于这种行为看起来并不是我们所期望的。
private拷贝控制
在新标准发布之前,类是通过将其拷贝构造函数和拷贝赋值运算符声明为private的来阻止拷贝:

由于析构函数是public的,用户可以定义PrivateCopy类型的对象。但是,由于拷贝构造函数和拷贝赋值运算符是private的,用户代码将不能拷贝这个类型的对象。但是,友元和成员函数仍旧可以拷贝对象。为了阻止友元和成员函数进行拷贝,我们将这些拷贝控制成员声明为private的,但并不定义它们。
13-1 c++拷贝控制:拷贝赋值与销毁的更多相关文章
- 【C++ Primer 第13章】1. 拷贝控制、赋值和销毁
拷贝控制.赋值和销毁 如果一个构造函数的第一个参数是自身类的引用,且额外的参数都有默认值,则此构造函数是拷贝控制函数(拷贝构造函数不应该是explicit的). 如果我们没有为一个类定义拷贝构造函数, ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝、赋值与销毁
拷贝构造函数 一个构造函数的第一个参数是自身类类型的引用,额外的参数(如果有)都有默认值,那么这个构造函数是拷贝构造函数.拷贝构造函数的第一个参数必须是一个引用类型. 合成的拷贝构造函数 在我们没 ...
- [C++ Primer] : 第13章: 拷贝控制
拷贝, 赋值与销毁 当定义一个类时, 我们显示地或隐式地指定在此类型的对象拷贝, 移动, 赋值和销毁时做什么. 一个类通过定义5种特殊的成员函数来控制这些操作, 包括: 拷贝构造函数, 拷贝赋值运算符 ...
- 【c++ Prime 学习笔记】第13章 拷贝控制
定义一个类时,可显式或隐式的指定在此类型对象上拷贝.移动.赋值.销毁时做什么.通过5种成员函数实现拷贝控制操作: 拷贝构造函数:用同类型的另一个对象初始化本对象时做什么(拷贝初始化) 拷贝赋值算符:将 ...
- 【C++ Primer 第13章】2. 拷贝控制和资源管理
拷贝控制和资源管理 • 类的行为像一个值.意味着它应该有自己的状态,当我们拷贝一个像值得对象时,副本和原对象是完全独立的,改变副本不会对原对象有任何影响. • 行为像指针的类则共享状态.当我们拷贝一个 ...
- 《C++ Primer》笔记 第13章 拷贝控制
拷贝和移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么.拷贝和移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么.析构函数定义了当此类型对象销毁时做什么.我们称这些操作为拷贝控制 ...
- C++ Primer 笔记——拷贝控制
1.如果构造函数的第一个参数是自身类类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数.拷贝构造函数的第一个参数必须是引用类型(否则会无限循环的调用拷贝构造函数). 2.如果没有为一个类 ...
- C++ Primer : 第十三章 : 拷贝控制之对象移动
右值引用 所谓的右值引用就是必须将引用绑定到右值的引用,我们通过&&来绑定到右值而不是&, 右值引用只能绑定到即将销毁的对象.右值引用也是引用,因此右值引用也只不过是对象的别名 ...
- C++ Primer : 第十三章 : 拷贝控制之拷贝控制和资源管理
定义行为像值的类 行为像值的类,例如标准库容器和std::string这样的类一样,类似这样的类我们可以简单的实现一个这样的类HasPtr. 在实现之前,我们需要: 定义一个拷贝构造函数,完成stri ...
- C++的那些事:类的拷贝控制
1,什么是类的拷贝控制 当我们定义一个类的时候,为了让我们定义的类类型像内置类型(char,int,double等)一样好用,我们通常需要考下面几件事: Q1:用这个类的对象去初始化另一个同类型的对象 ...
随机推荐
- 《重学Java设计模式》笔记——建造者模式
1. 建造者模式可以解决什么问题 我家里有各种形状的瓷器,盘子或者碗.虽然形状不同,但是所用的材料基本上是一样的,比如土.水.釉.彩这些基本的东西. 但是做不同款式的瓷器,方法是不同的.假如说我现在已 ...
- 2023 CCPC 哈尔滨游记
board zsy 11.3 下了高代课跟教练聊了会,以为差点赶不上飞机了,结果还好.飞机上一直在看<笑傲江湖> 晚上本来想写作业的,还是摆了 拉 zsy 打雀魂,三人麻将到第二天了 11 ...
- kubeadm升级k8s之1.23.17->1.24.17
查看当前版本 [root@k8s-master31 ~]# kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EX ...
- Docker容器常用操作命令(镜像的上传、下载、导入、导出、创建、删除、修改、启动等)详解
1.docker镜像下载 docker pull [options] name [:tag@digest] name后边可以跟镜像标签或者镜像摘要(其实就是镜像的版本),如果不加任何东西,则会默认是在 ...
- Linux IP 命令
改MAC 地址 ip link set dev nic1 downip link set dev nic1 address 0c:42:a1:8f:a4:47ip link set dev nic1 ...
- 最小化安装killall不可用
最小化安装killall不可用 最小化安装Centos7.4后,发现killall命令不可用 使用了以下命令,查看软件包名: yum search killall 查找后发现应使用这个安装包 yum ...
- DOM & BOM – 用 Canvas 修图
前言 以前有写过一篇关于 canvas 处理图片的文章. 非常乱, 这篇做一个整理. 参考 Stack Overflow – HTML5 Canvas Rotate Image Stack Overf ...
- JavaScript – 数据类型
前言 写着 TypeScript 学习笔记, 顺便也写点 JS 的呗. 参考 JS数据类型分类和判断 阮一峰 – 数据类型 JS 数据类型 string number boolan undefined ...
- docker安装及基本的镜像拉取
docker 使用存储库安装 卸载它们以及相关的依赖项. yum remove docker \ docker-client \ docker-client-latest \ docker-commo ...
- 【赵渝强老师】管理Docker镜像
一.什么是Docker的镜像 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙 ...