12. 动态内存
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量;栈内存用来保存定义在函数内的非static对象。分配在静态内存或栈内存中的对象由编译器自动创建和销毁。除了静态内存和栈内存,程序还有一个内存池称为自由空间或堆,程序用堆存储动态分配的对象。动态分配对象的生存期与在哪里创建无关,只有当显式地被释放时才会被销毁。
12.1 动态内存与智能指针
动态内存的管理通过new和delete完成:
- new:在动态内存中为对象分配空间并返回一个指向该对象的指针;
- delete:接受一个动态对象的指针销毁该对象并释放与之关联的内存。
为了更容易使用动态内存,标准库提供两种智能指针类型(shared_ptr允许多个指针指向同一个对象,unique_ptr独占指向的对象)管理动态对象,它负责自动释放指向的对象,定义在memory头文件中。
12.1.1 shared_ptr类
解引用一个智能指针返回它指向的对象,在一个条件判断中使用智能指针效果为检测它是否为空:
make_shared<T>(args):
最安全的分配和使用动态内存的方法,调用make_shared函数可以在动态内存中分配一个对象并初始化,返回指向此对象的shared_ptr。
shared_ptr<T>p(q):
进行拷贝或赋值操作时,每个shared_ptr都会记录有多少其它shared_ptr指向相同的对象(基于引用计数)。
当shared_ptr计数器变为0时会自动释放自己所管理的对象(通过析构函数):
程序使用动态内存出于以下三种原因:
- 程序不知道自己需要使用多少对象;
- 程序不知道所需对象的准确类型;
- 程序需要在多个对象间共享数据;
这里举例说明第三种原因:
目前为止使用过的类中分配的资源与对应对象生存期一致,例如每个vector拥有自己的元素,拷贝一个vector时,原vector和副本vector中的元素相互分离:
定义Blob类保存一组元素,希望Blob对象的不同拷贝之间共享相同的元素(拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素):
当一个vector被销毁时,其中的元素也被销毁,而Blob对象共享相同的元素,b2被销毁时其中的元素必须保留。
定义一个管理string的StrBlob类:
size、empty、push_back成员通过指向底层的vector的data成员完成工作。
构造函数:
元素访问成员函数:
pop_back、front、back访问vector中的元素,在访问元素前用check检查元素是否存在。
check接受一个string参数将其传递给异常处理程序:
其他成员调用check后再完成工作:
12.1.2 直接管理内存
用new和delete分配和释放动态内存的方法相对于智能指针非常容易出错。
new:动态分配和初始化对象
new在自由空间构造一个int型对象并返回指向该对象的指针。
delete:传递的指针必须指向动态分配的内存
12.1.5 unique_ptr
只能有一个unique_ptr指向一个指定对象,当unique_ptr被销毁时,它指向的对象也被销毁。unique_ptr不支持普通的拷贝或赋值操作。
12.2 动态数组
12.2.1 new和数组
返回一个元素类型的指针。
12.2.2 allocator类
new和delete将构造/析构与内存分配/释放组合在了一起,使用allocator类可以将内存分配和对象构造分离开,当一个allocator对象分配内存时,会根据给定的对象类型确定恰当的内存大小。
12.3 使用标准库:文本查询程序
程序允许用户在一个给定文件中查询单词,查询结果是单词在文件中出现的次数及其所在行的列表。例如在文本中寻找单词element,输出结果为:
需求分析:
实现思路:
定义一个保存输入文件的类TextQuery,包含一个vector保存输入文件的文本和一个map关联每个单词和它出现的行号set,以及读取给定输入文件的构造函数和一个执行查询的操作;
定义一个返回所有查询结果的类QueryResult,有一个print函数进行结果打印。
TextQuery:
构造函数:
定义一个query函数:
QueryResult:
- string:保存查询单词
- shared_ptr:指向保存输入文件的vector
- shared_ptr:指向保存单词出现行号的set
最后打印结果。