# 第十二章 动态内存 - 对象的生命周期: - 全局对象在程序启动时分配,结束时销毁。 - 局部对象在进入程序块时创建,离开块时销毁。 - 局部`static`对象在第一次使用前分配,在程序结束时销毁。 - 动态分配对象:只能显式地被释放。 - 对象的内存位置: - **静态内存**用来保存局部`static`对象、类`static`对象、定义在任何函数之外的变量。 - **栈内存**用来保存定义在函数内的非`static`对象。 - **堆内存**,又称自由空间,用来存储**动态分配**的对象。 ## 动态内存与智能指针 - 动态内存管理: - `new`:在动态内存中为对象分配空间并返回一个指向该对象的指针。 - `delete`:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。 - 智能指针: - 管理动态对象。 - 行为类似常规指针。 - 负责自动释放所指向的对象。 - 智能指针也是模板。 ### shared_ptr类 **shared_ptr和unique_ptr都支持的操作**: | 操作 | 解释 | |-----|-----| | `shared_ptr sp` `unique_ptr 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(args)` | 返回一个`shared_ptr`,指向一个动态分配的类型为`T`的对象。使用`args`初始化此对象。 | | `shared_ptrp(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 p(q)` | `p`管理内置指针`q`所指向的对象;`q`必须指向`new`分配的内存,且能够转换为`T*`类型 | | `shared_ptr p(u)` | `p`从`unique_ptr u`那里接管了对象的所有权;将`u`置为空 | | `shared_ptr p(q, d)` | `p`接管了内置指针`q`所指向的对象的所有权。`q`必须能转换为`T*`类型。`p`将使用可调用对象`d`来代替`delete`。 | | `shared_ptr 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 u1` | 空`unique_ptr`,可以指向类型是`T`的对象。`u1`会使用`delete`来是释放它的指针。 | | `unique_ptr u2` | `u2`会使用一个类型为`D`的可调用对象来释放它的指针。 | | `unique_ptr 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 w` | 空`weak_ptr`可以指向类型为`T`的对象 | | `weak_ptr 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 u` | `u`可以指向一个动态分配的数组,整数元素类型为`T` | | `unique_ptr u(p)` | `u`指向内置指针`p`所指向的动态分配的数组。`p`必须能转换为类型`T*`。 | | `u[i]` | 返回`u`拥有的数组中位置`i`处的对象。`u`必须指向一个数组。 | ### allocator类 - 标准库`allocator`类定义在头文件`memory`中,帮助我们将内存分配和对象构造分离开。 - 分配的是原始的、未构造的内存。 - `allocator`是一个模板。 - `allocator alloc;` **标准库allocator类及其算法**: | 操作 | 解释 | |-----|-----| | `allocator 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`中。 - 在给定目的位置创建元素,而不是由系统分配内存给他们。