C++primer学习笔记——第13章拷贝控制
13.1拷贝、赋值与销毁
13.1.1拷贝构造函数
- 定义:
- 构造函数第一个参数是自身类型的引用。
- 任何额外参数都有默认值
- 合成拷贝构造函数:
- 如果没有自定义拷贝构造函数,则编译器定义一个默认拷贝构造函数。
- 与合成默认构造函数不同,即使定义了其它构造函数,也会合成默认拷贝构造函数。
- 合成的拷贝构造函数,将其参数的成员逐个拷贝到正在创建的对象中。除static外。
- 成员类型决定拷贝方式:
成员类型 | 拷贝方式 |
---|---|
类类型 | 使用其拷贝构造函数 |
内置类型 | 直接拷贝 |
数组 | 逐元素拷贝 |
数组元素是类类型 | 调用其拷贝构造函数 |
- 拷贝初始化:
- 直接初始化:要求编译器使用普通的函数匹配来选择构造函数
- 拷贝初始化:将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换。
- 拷贝初始化不仅在使用‘=’时发生,在下列情况也会发生
将一个对象作为实参传递给一个非引用类型的形参
从一个返回类型为非引用类型的函数返回一个对象
用花括号列表初始化
PS:某些类类型还会对他们所分配的对象使用拷贝初始化,例如,使用标准库容器的push、insert时,执行的是拷贝初始化,使用emplace时,执行的是直接初始化(构造元素)。
PS:如果拷贝构造函数的参数不是引用类型,则无法调用拷贝构造函数。因为值传递参数时会调用拷贝构造函数。
- 编译器可以绕过拷贝构造函数
例如:编译器可以改写代码
string null_book="99999";
为:
string null_book("99999");
但是,即使编译器绕过了拷贝构造函数,在此处,拷贝构造函数也必须是存在且可访问的。
13.1.2 拷贝赋值运算符
- 重载赋值运算符
- 本质上是函数
- 必须定义为成员函数
- 如果一个运算符是一个成员函数,其左侧运算对象绑定到this上
- 通常应该返回一个指向其左侧运算对象的引用
- 合成拷贝赋值运算符
- 与合成拷贝构造函数类似
13.1.3 析构函数
作用:析构函数释放释放对象使用的资源,销毁对象的非static数据成员。
用~开头,无返回值,无参数,不能重载。
- 析构函数的工作
- 构造函数:先初始化成员,再执行函数体
- 析构函数:先执行函数体,再销毁成员
PS:隐式销毁一个内置指针类型不会delete它所指向的对象。
PS:析构函数自身不直接销毁成员,成员是在析构函数体之后隐含的析构阶段被销毁的。也就是说可以自己析构的成员我们不需要管,只要在函数体中销毁我们自己申请的空间和一些其它的操作就可以了。
13.1.4 三/五法则
-
需要析构函数的类也需要拷贝和赋值操作
例如:假如在类中动态分配了一块内存,它的析构函数需要释放这块内存。但使用的是默认合成的拷贝构造函数的话,只会拷贝指向这块内存的指针,而没有分配新的内存。这样当一个对象被销毁时,另一个对象的指针也指向这块内容。产生的结果将是未定义的。 -
如果一个类需要拷贝构造函数,那么它也需要拷贝赋值运算符,反之亦然。
需要拷贝构造函数意味着这个类除了默认的拷贝操作之外需要一些额外的操作,赋值操作=析构+拷贝,那么对于赋值操作也需要。
13.1.6 阻止拷贝
例如 iostream类阻止了拷贝
- 定义删除的函数
- 使用=delete来定义删除的函数。
- 删除的函数:虽然我们声明了它们,但不能以任何方式使用它们。
- 可以对任何函数指定=delete,希望引导函数匹配过程时,删除函数也是有用的。
class NoCopy{
NoCopy(const NoCopy&)=delete; //阻止拷贝
NoCopy &operator=(const NoCopy&)=delete; //阻止赋值
- 析构函数不能是删除的成员(可以这样定义,但最好不要这样做)
- 析构函数是删除的则无法定义该类型的对象。
- 可以动态分配该类型的对象但无法释放。
- 合成的拷贝控制成员可能是删除的
- 如果类的某个成员的析构函数是删除的或无法访问:无合成的析构函数、合成默认构造函数、合成拷贝构造函数。
- 如果类的某个成员是引用类型或const类型:无合成的赋值运算符。
- 如果类的某个成员是引用类型但无类内初始化器(类内初值),或某个成员是const类型但它无默认的构造函数,则无合成的默认构造函数。
- 如果类的某个成员无合成的默认拷贝构造函数,则无合成的默认拷贝构造函数。
- 如果类的某个成员无合成的默认赋值运算符,则无合成的默认赋值运算符。
- private拷贝控制
- 通过将拷贝构造函数和赋值运算符声明为private的,并且不定义它们,可以预先阻止任何拷贝该类型对象的企图。试图拷贝将导致编译出错,成员或友元拷贝将导致链接出错。
PS:尽量使用=delete方式进行拷贝控制而非private。
一个重要原因是,如果程序对该函数进行了访问,使用=delete删除的函数函数总是能在编译时发现错误,而private方法在某些情况下直到链接时才能发现错误。并且private方法有时给出的错误类型更让人迷惑(比如指出你在访问权限方面出了错,而不是告诉你这个函数是被删除的)。此外,delete方法还可以控制函数的匹配,这点是private方法无法做到的。