# 第十二章 动态内存

- 对象的生命周期：
  - 全局对象在程序启动时分配，结束时销毁。
  - 局部对象在进入程序块时创建，离开块时销毁。
  - 局部`static`对象在第一次使用前分配，在程序结束时销毁。
  - 动态分配对象：只能显式地被释放。

- 对象的内存位置：
  - **静态内存**用来保存局部`static`对象、类`static`对象、定义在任何函数之外的变量。
  - **栈内存**用来保存定义在函数内的非`static`对象。
  - **堆内存**，又称自由空间，用来存储**动态分配**的对象。

## 动态内存与智能指针

- 动态内存管理：
  - `new`：在动态内存中为对象分配空间并返回一个指向该对象的指针。
  - `delete`：接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。
- 智能指针：
  - 管理动态对象。
  - 行为类似常规指针。
  - 负责自动释放所指向的对象。
  - 智能指针也是模板。

### shared_ptr类

**shared_ptr和unique_ptr都支持的操作**：

| 操作 | 解释 |
|-----|-----|
| `shared_ptr<T> sp`  `unique_ptr<T> up` | 空智能指针，可以指向类型是`T`的对象 |
| `p` | 将`p`用作一个条件判断，若`p`指向一个对象，则为`true` |
| `*p` | 解引用`p`，获得它指向的对象。 |
| `p->mem` | 等价于`(*p).mem` |
| `p.get()` | 返回`p`中保存的指针，要小心使用，若智能指针释放了对象，返回的指针所指向的对象也就消失了。 |
| `swap(p, q)` `p.swap(q)` | 交换`p`和`q`中的指针 |

**shared_ptr独有的操作**：

| 操作 | 解释 |
|-----|-----|
| `make_shared<T>(args)` | 返回一个`shared_ptr`，指向一个动态分配的类型为`T`的对象。使用`args`初始化此对象。 |
| `shared_ptr<T>p(q)` | `p`是`shared_ptr q`的拷贝；此操作会**递增**`q`中的计数器。`q`中的指针必须能转换为`T*` |
| `p = q` | `p`和`q`都是`shared_ptr`，所保存的指针必须能互相转换。此操作会**递减**`p`的引用计数，**递增**`q`的引用计数；若`p`的引用计数变为0，则将其管理的原内存释放。 |
| `p.unique()` | 若`p.use_count()`是1，返回`true`；否则返回`false` |
| `p.use_count()` | 返回与`p`共享对象的智能指针数量；可能很慢，主要用于调试。 |

- **使用动态内存的三种原因**：
  - 程序不知道自己需要使用多少对象（比如容器类）。
  - 程序不知道所需要对象的准确类型。
  - 程序需要在多个对象间共享数据。

### 直接管理内存

- 用`new`动态分配和初始化对象。
  - `new`无法为分配的对象命名（因为自由空间分配的内存是无名的），因此是返回一个指向该对象的指针。
  - `int *pi = new int(123);`
  - 一旦内存耗尽，会抛出类型是`bad_alloc`的异常。
- 用`delete`将动态内存归还给系统。
  - 接受一个指针，指向要释放的对象。
  - `delete`后的指针称为空悬指针（dangling pointer）。
- 使用`new`和`delete`管理动态内存存在三个常见问题：
  - 1.忘记`delete`内存。
  - 2.使用已经释放掉的对象。
  - 3.同一块内存释放两次。
- 坚持只使用智能指针可以避免上述所有问题。

### shared_ptr和new结合使用

**定义和改变shared_ptr的其他方法**：

| 操作 | 解释 |
|-----|-----|
| `shared_ptr<T> p(q)` | `p`管理内置指针`q`所指向的对象；`q`必须指向`new`分配的内存，且能够转换为`T*`类型 |
| `shared_ptr<T> p(u)` | `p`从`unique_ptr u`那里接管了对象的所有权；将`u`置为空 |
| `shared_ptr<T> p(q, d)` | `p`接管了内置指针`q`所指向的对象的所有权。`q`必须能转换为`T*`类型。`p`将使用可调用对象`d`来代替`delete`。 |
| `shared_ptr<T> p(p2, d)` | `p`是`shared_ptr p2`的拷贝，唯一的区别是`p`将可调用对象`d`来代替`delete`。 |
| `p.reset()` | 若`p`是唯一指向其对象的`shared_ptr`，`reset`会释放此对象。若传递了可选的参数内置指针`q`，会令`p`指向`q`，否则会将`p`置空。若还传递了参数`d`，则会调用`d`而不是`delete`来释放`q`。 |
| `p.reset(q)` | 同上 |
| `p.reset(q, d)` | 同上 |

### 智能指针和异常

- 如果使用智能指针，即使程序块由于异常过早结束，智能指针类也能确保在内存不需要的时候将其释放。
- **智能指针陷阱**：
  - 不用相同的内置指针初始化（或`reset`）多个智能指针
  - 不`delete get()`返回的指针。
  - 如果你使用`get()`返回的指针，记得当最后一个对应的智能指针销毁后，你的指针就无效了。
  - 如果你使用智能指针管理的资源不是`new`分配的内存，记住传递给它一个删除器。

### unique_ptr

- 某一个时刻只能有一个`unique_ptr`指向一个给定的对象。
- 不支持拷贝或者赋值操作。
- 向后兼容：`auto_ptr`：老版本，具有`unique_ptr`的部分特性。特别是，不能在容器中保存`auto_ptr`，也不能从函数返回`auto_ptr`。

**unique_ptr操作**:

| 操作 | 解释 |
|-----|-----|
| `unique_ptr<T> u1` | 空`unique_ptr`，可以指向类型是`T`的对象。`u1`会使用`delete`来是释放它的指针。 |
| `unique_ptr<T, D> u2` | `u2`会使用一个类型为`D`的可调用对象来释放它的指针。 |
| `unique_ptr<T, D> u(d)` | 空`unique_ptr`，指向类型为`T`的对象，用类型为`D`的对象`d`代替`delete` |
| `u = nullptr` | 释放`u`指向的对象，将`u`置为空。 |
| `u.release()` | `u`放弃对指针的控制权，返回指针，并将`u`置空。 |
| `u.reset()` | 释放`u`指向的对象 |
| `u.reset(q)` | 令`u`指向`q`指向的对象 |
| `u.reset(nullptr)` | 将`u`置空 |

### weak_ptr

- `weak_ptr`是一种不控制所指向对象生存期的智能指针。
- 指向一个由`shared_ptr`管理的对象，不改变`shared_ptr`的引用计数。
- 一旦最后一个指向对象的`shared_ptr`被销毁，对象就会被释放，不管有没有`weak_ptr`指向该对象。

**weak_ptr操作**:

| 操作 | 解释 |
|-----|-----|
| `weak_ptr<T> w` | 空`weak_ptr`可以指向类型为`T`的对象 |
| `weak_ptr<T> w(sp)` | 与`shared_ptr`指向相同对象的`weak_ptr`。`T`必须能转换为`sp`指向的类型。 |
| `w = p` | `p`可以是`shared_ptr`或一个`weak_ptr`。赋值后`w`和`p`共享对象。 |
| `w.reset()` | 将`w`置为空。 |
| `w.use_count()` | 与`w`共享对象的`shared_ptr`的数量。 |
| `w.expired()` | 若`w.use_count()`为0，返回`true`，否则返回`false` |
| `w.lock()` | 如果`expired`为`true`，则返回一个空`shared_ptr`；否则返回一个指向`w`的对象的`shared_ptr`。 |

## 动态数组

### new和数组

- `new`一个动态数组：
  - 类型名之后加一对方括号，指明分配的对象数目（必须是整型，不必是常量）。
  - 返回**指向第一个对象的指针**。
  - `int *p = new int[size];`

- `delete`一个动态数组：
  - `delete [] p;`

- `unique_ptr`和数组：
  - 指向数组的`unique_ptr`不支持成员访问运算符（点和箭头）。

| 操作 | 解释 |
|-----|-----|
| `unique_ptr<T[]> u` | `u`可以指向一个动态分配的数组，整数元素类型为`T` |
| `unique_ptr<T[]> u(p)` | `u`指向内置指针`p`所指向的动态分配的数组。`p`必须能转换为类型`T*`。 |
| `u[i]` | 返回`u`拥有的数组中位置`i`处的对象。`u`必须指向一个数组。 |
  
### allocator类

- 标准库`allocator`类定义在头文件`memory`中，帮助我们将内存分配和对象构造分离开。
- 分配的是原始的、未构造的内存。
- `allocator`是一个模板。
- `allocator<string> alloc;`

**标准库allocator类及其算法**：

| 操作 | 解释 |
|-----|-----|
| `allocator<T> a` | 定义了一个名为`a`的`allocator`对象，它可以为类型为`T`的对象分配内存 |
| `a.allocate(n)` | 分配一段原始的、未构造的内存，保存`n`个类型为`T`的对象。 |
| `a.deallocate(p, n)` | 释放从`T*`指针`p`中地址开始的内存，这块内存保存了`n`个类型为`T`的对象；`p`必须是一个先前由`allocate`返回的指针。且`n`必须是`p`创建时所要求的大小。在调用`deallocate`之前，用户必须对每个在这块内存中创建的对象调用`destroy`。 |
| `a.construct(p, args)` | `p`必须是一个类型是`T*`的指针，指向一块原始内存；`args`被传递给类型为`T`的构造函数，用来在`p`指向的内存中构造一个对象。 |
| `a.destroy(p)` | `p`为`T*`类型的指针，此算法对`p`指向的对象执行析构函数。 |

**allocator伴随算法**：

| 操作 | 解释 |
|-----|-----|
| `uninitialized_copy(b, e, b2)` | 从迭代器`b`和`e`给定的输入范围中拷贝元素到迭代器`b2`指定的未构造的原始内存中。`b2`指向的内存必须足够大，能够容纳输入序列中元素的拷贝。 |
| `uninitialized_copy_n(b, n, b2)` | 从迭代器`b`指向的元素开始，拷贝`n`个元素到`b2`开始的内存中。 |
| `uninitialized_fill(b, e, t)` | 在迭代器`b`和`e`执行的原始内存范围中创建对象，对象的值均为`t`的拷贝。 |
| `uninitialized_fill_n(b, n, t)` | 从迭代器`b`指向的内存地址开始创建`n`个对象。`b`必须指向足够大的未构造的原始内存，能够容纳给定数量的对象。 |

- 定义在头文件`memory`中。
- 在给定目的位置创建元素，而不是由系统分配内存给他们。
