第12章_动态内存

动态内存与智能指针

  • 默认初始化的智能指针中保存着一个空指针
  • 智能指针的使用方式与普通指针类似
  • 解引用一个智能指针返回它指向的对象
  • 如果在一个条件判断中使用智能指针, 效果就是检测它是否为空

shared_ptr 类

  • p->get(): 返回 p 中保存的指针(要小心使用, 若智能指针释放了其对象, 返回的指针所指向的对象也就消失了)
  • swap(p, q) \ p.swap(q): 交换 p 和 q 中的指针
  • 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.use_count(): 返回与 p 共享对象的智能指针数量(可能很慢, 主要用于调试)
  • p.unique(): 若 p.use_count() 为 1, 返回 true, 否则返回 false

直接管理内存

shared_ptr 和 new 结合使用

  • std::shared_ptr p(new int{1024});
  • 不能将一个内置指针隐式转换为一个智能指针, 必须使用直接初始化形式
  • std::shard_ptr p(u): p 从 unique_ptr u 那里接管了对象的所有权, 将 u 置为空
  • shared_ptrp(q, d): p 接管了内置指针 q 所指的对象的所有权. q 必须能转换为 T* 类型. p 将使用可调用对象 d 来代替 delete
  • shared_ptr p(p2, d): p 是 shared_ptr p2 的拷贝, 为一的区别是 p 将用可调用对象 d 来代替 delete
  • p.reset() \ p.reset(q) \ p.reset(q, d): 若 p 是唯一指向其对象的 shared_ptr, reset 会释放此对象, 若传递了可选的参数内置指针 q, 会令 p 指向 q, 否则会将 p 置为空. 若还传递了参数 d, 将会调用 d 而不是 delete 来释放 q

智能指针和异常

unique_ptr

  • std::unique_ptrp(new int{1024});
  • unique_ptr 拥有它指向的对象, 因此不支持普通的拷贝或赋值操作
  • std::unique_ptr<T, D> u: 使用一个类型为 D 的可调用对象来释放指针
  • std::unique_ptr<T, D> u(d): 用类型为 D 的对象 d 代替 delete
  • u = nullptr: 释放 u 指向的对象, 将 u 置为空
  • u.release(): u 放弃对指针的控制器, 返回指针, 并将 u 置为空
  • u.reset() \ u.reset(q) \ u.reset(nullptr): 释放 u 指向的对象, 如果提供了内置指针 q, 令 u 指向这个对象; 否则将 u 置为空
  • 不能拷贝 unique_ptr 的规则有一个例外: 可以拷贝或赋值一个将要销毁的 unique_ptr, 最常见的例子是从函数返回一个 unique_ptr
    1
    2
    3
    4
    5
    6
    7
    8
    9
    std::unique_ptr<int> clone(int p) {
    return std::unique_ptr<int>(new int{p});
    }

    std::unique_ptr<int> clone(int p) {
    std::unique_ptr<int> ret(new int{p});
    ...
    return ret;
    }

weak_ptr

  • weak_ptr 是一种不控制所指向对象生存期的智能指针, 它指向一个 shared_ptr 管理的对象
  • 将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数
  • 一旦最后一个指向对象的 shared_ptr 被销毁, 对象就会被释放, 即使有 weak_ptr 指向对象, 对象也还是会被释放
  • weak_ptr w(sp): 与 shared_ptr sp 指向相同对象的 weak_ptr, T 必须能转换为 sp 指向的类型
  • w = p: p 可以是一个 shared_ptr 或一个 weak_ptr
  • 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(由于对象可能不存在, 不要直接使用 weak_ptr 访问对象, 而必须调用 lock)

动态数组

new 和数组

  • type *p = new type[size];
  • delete [] p;
  • 动态分配一个空数组是合法的
    1
    2
    char arr[0]; // error: 不能定义长度为 0 的数组
    char *cp = new char[0]; // yes, 但 cp 不能解引用
  • 标准库提供了一个可以管理 new 分配的数组的 unique_ptr 版本(可以使用下标运算符来访问数组中的元素)
    1
    2
    std::unique_ptr<int[]> up(new int[10]);
    up.release();
  • shared_ptr 不直接支持管理动态数组. 如果希望使用 shared_ptr 管理一个动态数组, 必须提供自己定义的删除器. 如果为定义删除器, 则代码是为定义的, 因为默认情况下, shared_ptr 使用 delete 销毁它所指向的对象. 如果为定义删除器, 则代码是为定义的, 因为默认情况下, shared_ptr 使用 delete 销毁它所指向的对象.(shared_ptr 未定义下标运算符, 而且智能指针类型不支持指针算术运算, 因此, 为了访问数组中的元素, 必须使用 get 获取一个内置指针去访问)
    1
    2
    std::unique_ptr<int> sp(new int[10], [](int *p){ delete [] p;});
    sp.reset();

allocator 类

  • new 有一些灵活性上的局限, 其中一方面表现在它将内存分配和对象构造组合在了一起. 当分配一块大内存时, 通常计划在这块内存上按需构造对象, 在此情况下, 希望将内存分配和对象构造分离
  • 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 指向的内存中构造一个对象(为了使用 allocate 返回的内存, 必须用 construct 构造对象)
  • a.destroy(p): p 为 T* 类型的指针, 此算法对 p 指向的对象执行析构函数(每个构造之后的元素需单独调用)

拷贝和填充为初始化内存的算法

  • uninitialized_copy(b, e, b2)
  • uninitialized_copy_n(b, n, b2)
  • uninitialized_fill(b, e, t)
  • uninitialized_fill_n(b, n, t)