动态内存与智能指针
C++中对象的生存周期以后再谈。
除了静态内存和栈内存,每个程序还拥有内存池。这部分内存被称作自由空间(free store)或堆(heap)。程序用堆来存储动态分配的对象。
为了更容易地使用动态内存,新的标准库提供了两种智能指针(smart pointer)类型来管理动态对象。
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指的对象。都定义在 memory 头文件中。
- shared_ptr 允许多个指针指向同一个对象;
- unique_ptr 则“独占”所指向的对象;
- 还有一种名为 weak_ptr 的伴随类,它是一种弱引用,指向 shared_ptr 所管理的对象。
1、是什么?
类似vector,智能指针也是模板。因此在创建一个智能指针的时候必须提供额外的信息——指针指向的类型。
shared_ptr<string> p1; // shared_ptr, 可以指向 string
shared_ptr<list<int>> p2; // shared_ptr, 可以指向 int 的 list
默认初始化的智能指针中保存着一个空指针。
smart 表现在什么地方?shared_ptr 模板中有一个counter,用来保存有多少个指针指向同一个对象。只有当这个counter为0的时候,shared_ptr 才会把之前申请的动态内存给释放掉。
每一个 shared_ptr 对象都有一个成员函数——析构函数,通过这个函数来完成销毁工作。shared_ptr 的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr 的析构函数就会销毁对象,并释放它占用的内存。
2、怎么用?
- 如何声明一个shared_ptr 对象?
shared_ptr<string> p1; // 空智能指针, 可以指向 string
- 如何初始化一个shared_ptr 对象?如何对一个shared_ptr 对象进行赋值?
当进行拷贝或者赋值操作时,每个 shared_ptr 都会记录有多少个其他 shared_ptr 指向相同的对象。
// 指向一个值为 42 的 int 的shared_ptr
// make_shared<T>(args) 必须与 T 的某个构造函数相匹配
shared_ptr<int> p1 = make_shared<int>(42);
// 如果不传入任何参数,就会进行值初始化
shared_ptr<int> p2 = make_shared<int>();
// shared_ptr<T> p(q) p 是 q 的拷贝,会递增 q 中的计数器
shared_ptr<int> p3(p1) // p1 和 p3 指向相同的对象,此对象有两个引用者
// 还有一种初始化 shared_ptr 的方法
shared_ptr<int> p4 = new int(42); // 错误:必须使用直接初始化形式,使用了隐式转换,不能把正常指针隐式转换为shared_ptr
shared_ptr<int> p5(new int(43)); // 正确:使用直接初始化形式
// 思考为什么必须使用直接初始化形式??
-
计数器
无论何时我们拷贝一个 shared_ptr,计数器都会递增。
计数器递增的情况:当用一个shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时;
计数器递减的情况:当我们给shared_ptr 赋予一个新值或是shared_ptr 被销毁(局部的shared_ptr 离开其作用域)。一旦一个shared_ptr 的计数器变为0,它就会自动释放自己所管理的资源。
auto r = make_shared<int>(42); // r 指向的 int 只有一个引用者
r = q; // 给 r 赋值,令其指向另一个地址
// 递增 q 指向的对象的引用计数
// 递减 r 原来指向的对象的引用计数
// r 原来指向的对象已没有引用者,会自动释放
注意:
1、shared_ptr 在无用之后忘记了销毁,程序仍会正确执行,这就造成了浪费内存。shared_ptr 在无用之后仍然保留的一种可能情况是,将shared_ptr 存放在一个容器中,随后重排了容器,从而不再需要某些元素。在这种情况下,应该确保使用erase 删除那些不再需要的shared_ptr 元素。
2、程序使用动态内存出于以下三种情况:
a、程序不知道自己需要使用多少对象;
b、程序不知道所需对象的准确类型;
c、程序需要在多个对象间共享数据。
容易出错的地方:
1、不要混合使用普通指针和智能指针;
2、不要使用get() 初始化另一个智能指针或为智能指针赋值;想想为什么?