C++ 支持动态分配对象,动态分配的对象的生存期与它们在哪里创建是无关的,只有当显式地被释放时,这些对象才会被销毁。

内存空间:

  • 静态内存:保存局部 static 对象,类 static 数据成员、定义在任何函数之外的变量;使用前分配,程序结束时销毁
  • 栈内存:保存定义在函数内的非 static 对象;仅在其所在的程序块运行时才存在
  • 堆:存储动态分配的对象;生命期由程序控制

    动态内存与智能指针

    C++ 动态内存的管理是通过一对运算符来完成的

  • new,在动态内存中为对象分配空间并返回一个指向该对象的指针

  • delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存

确保在正确的时间释放内存是极其困难的,为了更安全容易地使用动态内存,新的标准库提供了两种智能指针来管理动态对象,智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象

  • shared_ptr 允许多个指针指向同一个对象,weak_ptr 指向 shared_ptr 所指向的对象
  • unique_ptr 独占所指向的对象

    shared_ptr 类

    类似 vector,智能指针也是模板

    1. // 默认初始化的智能指针中保存着一个空指针
    2. shared_ptr<string> p1; // shared_ptr,可以指向 string
    3. shared_ptr<list<int>> p2; // shared_ptr,可以指向 int 的 list

    make_shared 函数
    最安全的分配和使用动态内存的方法是调用一个名为 make_shaerd 的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的 shaerd_ptr

    1. auto p = make_shaerd<vector<string>>(); // p 指向一个动态分配的空 vector<string>

    当进行拷贝和赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象

    1. auto p = make_shaerd<int>(42); // p 指向的对象只有 p 一个引用者
    2. auto q(p); // q 和 p 指向相同对象,此对象有两个引用者

    可以认为每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数,无论何时拷贝一个 shared_ptr,计数器都会递增,例如用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值。

    1. auto r = make_shared<int>(42); // r 指向 int 只有一个引用者
    2. r = q; // 给 r 赋值,令它指向另一个地址
    3. // 递增 q 指向的对象的引用计数
    4. // 递减 r 原来指向的对象的引用计数
    5. // r 原来指向的对象已没有引用者,会自动释放

    若将 shared_ptr 存放于一个容器中,而后不再需要全部元素,而只是用其中一部分,要记得用 erase 删除不再需要的哪些元素。

    定义 StrBlob 类

    程序使用动态内存出于以下三种原因

  • 程序不知道自己需要使用多少对象

  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象间共享数据

我们希望定义一个名为 Blob 的类,保存一组元素,与容器不同,我们希望 Blob 对象的不同拷贝之间共享相同的元素。

  1. class StrBlob {
  2. public:
  3. typedef vector<string>::size_type size_type;
  4. StrBlob();
  5. StrBlob(initializer_list<string> il);
  6. size_type size() const { return data->size(); }
  7. bool empty() const { return data->empty(); }
  8. // 添加和删除元素
  9. void push_back(const string &t) { data->push_back(t); }
  10. void pop_back();
  11. // 元素访问
  12. string& front();
  13. string& back();
  14. private:
  15. shared_ptr<vector<string>> data;
  16. // 如果 data[i] 不合法,抛出一个异常
  17. void check(size_type i, const string &msg) const;
  18. };
  19. StrBlob::StrBlob(): data(make_shared<vector<string>>()) {}
  20. StrBlob::StrBlob(initializer_list<string> il):
  21. data(make_shared<vector<string>>(il)) {}

直接管理内存

C++ 定义了两个运算符来分配和释放动态内存;运算符 new 分配内存,delete 释放 new 分配的内存。
在自由空间分配的内存是无名的,因此 new 无法为其分配的对象命名,而是返回一个指向该对象的指针

  1. int *pi = new int; // pi 指向一个动态分配的、未初始化的无名对象

可以使用直接初始化方式来初始化一个动态分配的对象

  1. int *pi = new int(1024);
  2. string *ps = new string(10, '9');
  3. vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};

为了防止内存被耗尽,在动态内存使用完毕后,通过 delete 表达式将动态内存归还给系统

  1. delete p; // p 必须指向一个动态分配的对象或者是一个空指针
  2. // delete 执行两个动作,销毁给定的指针指向的对象,并释放对应的内存

释放一块并非 new 分配的内存,或者将相同的指针释放多次,其行为是未定义的

  1. int i, *pi1 = &i, *pi2 = nullptr;
  2. double *pd = new double(33), *pd2 = pd;
  3. delete i; // 错误,i 不是一个指针
  4. delete pi1; // 未定义,pi1 指向一个局部变量
  5. delete pd; // 正确
  6. delete pd2; // 未定义,pd2 指向的内存已经被释放了
  7. delete pi2; // 正确,释放一个空指针是没有错误的

由 shared_ptr 管理的内存在最后一个 shared_ptr 销毁时会被自动释放,但通过内置指针来管理的内存,生存期直到被释放为止。

shared_ptr 与 new 结合使用

接受指针参数的智能指针构造函数是 explicit 的,因此不能将内置指针隐式转换为一个智能指针,必须使用直接初始化方式

  1. shared_ptr<int> p1 = new int(1024); // 错误:必须使用直接初始化形式
  2. shared_ptr<int> p2(new int(1024)); // 正确

unique_ptr

与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向一个给定对象,当 unique_ptr 被销毁时,它所指向的对象也被销毁。
初始化 unique_ptr 必须采用直接初始化方式

  1. unique_ptr<double> p1; // 可以指向一个 double 的 unique_ptr
  2. unique_ptr<int> p2(new int(42)); // p2 指向一个值为 42 的 int

由于一个 unique_ptr 独占它指向的对象,因此不支持普通的拷贝或赋值操作

weak_ptr

weak_ptr 不控制所指向的对象的生存期,它指向由一个 shared_ptr 管理的对象,weak_ptr 不能改变引用计数,也不能阻止对象被释放。