# 第十三章 拷贝控制

**拷贝控制操作**（copy control）:

- 拷贝构造函数（copy constructor）
- 拷贝赋值运算符（copy-assignment operator）
- 移动构造函数（move constructor）
- 移动赋值函数（move-assignement operator）
- 析构函数（destructor）

## 拷贝、赋值和销毁

### 拷贝构造函数

- 如果一个构造函数的第一个参数是**自身类类型的引用**，且任何额外参数都有默认值，则此构造函数是**拷贝构造函数**。
- `class Foo{ public: Foo(const Foo&); }`
- **合成的拷贝构造函数**（synthesized copy constructor）：会将参数的成员逐个拷贝到正在创建的对象中。
- **拷贝初始化**：
  - 将右侧运算对象拷贝到正在创建的对象中，如果需要，还需进行类型转换。
  - 通常使用拷贝构造函数完成。
  - `string book = "9-99";`
  - 出现场景：
    - 用`=`定义变量时。
    - 将一个对象作为实参传递给一个非引用类型的形参。
    - 从一个返回类型为非引用类型的函数返回一个对象。
    - 用花括号列表初始化一个数组中的元素或者一个聚合类中的成员。

### 拷贝赋值运算符

- **重载赋值运算符**：
  - 重写一个名为`operator=`的函数.
  - 通常返回一个指向其左侧运算对象的引用。
  - `Foo& operator=(const Foo&);`
- **合成拷贝赋值运算符**：
  - 将右侧运算对象的每个非`static`成员赋予左侧运算对象的对应成员。

### 析构函数

- 释放对象所使用的资源，并销毁对象的非`static`数据成员。
- 名字由波浪号接类名构成。没有返回值，也不接受参数。
- `~Foo();`
- 调用时机：
  - 变量在离开其作用域时。
  - 当一个对象被销毁时，其成员被销毁。
  - 容器被销毁时，其元素被销毁。
  - 动态分配的对象，当对指向它的指针应用`delete`运算符时。
  - 对于临时对象，当创建它的完整表达式结束时。
- **合成析构函数**：
  - 空函数体执行完后，**成员会被自动销毁。**
  - 注意：析构函数体本身并不直接销毁成员。

### 三/五法则

- 需要析构函数的类也需要拷贝和赋值操作。
- 需要拷贝操作的类也需要赋值操作，反之亦然。

### 使用=default

- 可以通过将拷贝控制成员定义为`=default`来显式地要求编译器生成合成的版本。
- 合成的函数将隐式地声明为内联的。

### 阻止拷贝

- 大多数类应该定义默认构造函数、拷贝构造函数和拷贝赋值运算符，无论是隐式地还是显式地。
- 定义删除的函数：`=delete`。
- 虽然声明了它们，但是不能以任何方式使用它们。
- 析构函数不能是删除的成员。
- 如果一个类有数据成员不能默认构造、拷贝、复制或者销毁，则对应的成员函数将被定义为删除的。
- 老版本使用`private`声明来阻止拷贝。

## 拷贝控制和资源管理

- 类的行为可以像一个值，也可以像一个指针。
  - 行为像值：对象有自己的状态，副本和原对象是完全独立的。
  - 行为像指针：共享状态，拷贝一个这种类的对象时，副本和原对象使用相同的底层数据。

## 交换操作

- 管理资源的类通常还定义一个名为`swap`的函数。
- 经常用于重排元素顺序的算法。
- 用`swap`而不是`std::swap`。

## 对象移动

- 很多拷贝操作后，原对象会被销毁，因此引入移动操作可以大幅度提升性能。
- 在新标准中，我们可以用容器保存不可拷贝的类型，只要它们可以被移动即可。
- 标准库容器、`string`和`shared_ptr`类既可以支持移动也支持拷贝。`IO`类和`unique_ptr`类可以移动但不能拷贝。

### 右值引用

- 新标准引入右值引用以支持移动操作。
- 通过`&&`获得右值引用。
- 只能绑定到一个将要销毁的对象。
- 常规引用可以称之为左值引用。
- 左值持久，右值短暂。

**move函数**：

- `int &&rr2 = std::move(rr1);`
- `move`告诉编译器，我们有一个左值，但我希望像右值一样处理它。
- 调用`move`意味着：除了对`rr1`赋值或者销毁它外，我们将不再使用它。

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

- **移动构造函数**：
  - 第一个参数是该类类型的一个引用，关键是，这个引用参数是一个**右值引用**。
  - `StrVec::StrVec(StrVec &&s) noexcept{}`
  - 不分配任何新内存，只是接管给定的内存。
- **移动赋值运算符**：
  - `StrVec& StrVec::operator=(StrVec && rhs) noexcept{}` 
- 移动右值，拷贝左值。
- 如果没有移动构造函数，右值也被拷贝。
- 更新三/五法则：如果一个类定义了任何一个拷贝操作，它就应该定义所有五个操作。
- 移动迭代器：
  - `make_move_iterator`函数讲一个普通迭代器转换为一个移动迭代器。
- 建议：小心地使用移动操作，以获得性能提升。

### 右值引用和成员函数

- 区分移动和拷贝的重载函数通常有一个版本接受一个`const T&`，而另一个版本接受一个`T&&`。
- 引用限定符：
  - 在参数列表后面防止一个`&`，限定只能向可修改的左值赋值而不能向右值赋值。
  
