c++primer 第12章 动态内存

第12章 动态内存

  • 生命期
    • 全局对象:程序启动时分配,程序结束时销毁
    • 局部自动对象:进入定义程序块时创建,离开块时销毁
    • 局部static对象:第一次使用前分配,在程序结束时销毁
    • 动态分配对象:只当显式地被释放时,对象才会被销毁
  • 智能指针类型
    • 当对象应该被释放时,指向它的智能指针可以确保自动释放它
  • 内存
    • 静态内存:局部static对象、类static数据成员、定义在任何函数之外的变量
    • 栈内存:函数内非static对象
      • 分配在静态内存和栈内存中的对象由编译器自动创建和销毁
    • 内存池:自由空间/堆 存储动态分配的对象

12.1 动态内存与智能指针

  • new: 在动态内存中分配空间并返回一个指向该对象的指针
  • delete:接受动态对象指针,销毁该对象,释放与之关联的的内存
  • 动态内存使用问题:确保在正确的时间释放内存
    • 内存泄漏:忘记释放内存
    • 引用非法内存:尚有指针引用内存的情况下就释放了它
  • 智能指针:负责自动释放所指向的对象 memory头文件
    • share_ptr:允许多个指针指向同一个对象
    • unique_ptr:独占所指向的对象
    • weak_ptr:弱引用,指向shared_ptr所管理的对象

12.1.1 share_ptr类

  • 智能指针是模板,需提供额外的信息,指针可以指向的类型
表12.1 shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp unique_ptr<T> up 空智能指针,可以指向类型为T的对象
p p用作一个条件判断,若p指向一个对象,则为true
*p 解引用p,获得它指向的对象。
p->mem 等价于(*p).mem
p.get() 返回p中保存的指针。要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
swap(p, q) p.swap(q) 交换pq中的指针
表12.2 shared_ptr独有的操作
make_shared<T>(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。
shared_ptr<T>p(q) pshared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p = q pq都是shared_ptr,所保存的指针必须能互相转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。
p.unique() p.use_count()是1,返回true;否则返回false
p.use_count() 返回与p共享对象的智能指针数量;可能很慢,主要用于调试。
  • make_shared函数
    • 安全的分配和使用动态内存。在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。
    • 使用时须指定想要创建的对象的类型
    • 与emplace类似,用其参数来构造给定类型的对象;若不传递任何参数,对象就会进行值初始化
    • 通常使用auto定义一个对象来保存make_shared的结果
  • share_ptr的拷贝和赋值
    • 进行拷贝和赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象
    • 每个shared_ptr都有一个关联的计数器(引用计数);
      • 拷贝(一个shared_ptr初始化另一个shared_ptr;将它作为参数传递给一个函数;作为参数传递给一个函数;作为参数的返回值):递增
      • 给shared_ptr赋予一个新值或是shared_ptr被销毁(局部shared_ptr离开作用域):递减
      • shared_ptr计数器变为0,自动释放自己所管理的对象
  • shared_ptr自动销毁所管理的对象
    • 析构函数:当指向对象的最后一个shared_ptr被销毁时,会自动销毁此对象
  • shared_ptr还会自动释放相关联的内存
    • 当对象不再被使用时,shared_ptr类会自动释放动态对象
    • shared_ptr类保证只要有任何shared_ptr对象引用它,它就不会被释放掉
    • shared_ptr无用后仍然保留的情况:将shared_ptr存放在一个容器中,重排了容器,不再需要某些元素。(应确保使用erase删除不再需要的shared_ptr元素)
  • 使用了动态生存期的资源的类
    • 使用动态内存原因
        1. 不知道自己需要使用多少对象
        1. 不知道所需对象的准确类型
        1. 需要在多个对象间共享数据
    • 已使用过的类,分配的资源都与对应对象生存期一致
    • 某些类分配的资源具有与原对象相独立的生存期
  • 定义StrBlod类
    • 通过设置shared_ptr实现数据共享
    • 实现部分成员,通过指向底层容器完成工作
  • StrBlod构造函数
    • 初始化列表初始化data成员,指向动态分配的vector
  • 元素访问成员函数
    • 试图访问元素前须先检查元素是否存在
    • check接受一个给定索引和string参数,检查给定索引是否在合法范围内,并将string参数传递给异常处理程序(描述错误内容)
  • StrBlod的拷贝、赋值和销毁
    • 当拷贝、赋值或者销毁一个StrBlod对象时,它的shared_ptr成员会被拷贝、赋值或销毁
    • 拷贝shared_ptr会递增赋值号右侧shared_ptr的引用计数,而递减左侧shared_ptr的引用计数,当sahred_ptr引用计数变为0,它指向的对象会被自动销毁

12.1.2 直接管理内存

  • 两个运算符 分配和释放内存
    • new 分配内存
    • delete 释放new分配的内存
    • 不能依赖对象拷贝、赋值和销毁操作的任何默认定义
  • 使用new动态分配和初始化对象
    • 自由分配的内存是无名的,new返回一个指向给该对象的指针
    • new type 动态分配对象是默认初始化的
      • 内置类型或者组合类型的对象的值将是未定义的
      • 类类型对象将用默认构造函数进行初始化
    • new type(para) 直接初始化 传统构造(圆括号)/列表初始化{花括号}
      • 当括号内仅有单一初始化器时可以使用auto推断想要分配的对象的类型
    • new type() 值初始化 内置类型初始化为0
      • 类中依赖于编译器合成的默认构造函数的内置类型成员若未在类内被初始化值也是未定义的
  • 动态分配的const对象
    • 用new分配的const对象是合法的,返回的是一个指向const的指针
      • 一个动态分配的const对象必须进行初始化
      • 定义了默认构造函数的类类型const对象可以隐式初始化,其他类型对象必须显示初始化
  • 内存耗尽
    • 一个程序用光了它所有可用的内存,new表达式就会失败
    • 当new不能分配所要求的内存空间,它会抛出一个类型为bad_alloc的异常
    • 可以改变使用new的方式来阻止它抛出异常
      • new(nothrow) int 如果分配失败,new返回一个指针
      • 定位new 允许向new传递额外的参数
  • 释放动态内存
    • delete 销毁给定的指针指向的对象,释放对应的内存(防止内存耗尽)
  • 指针值和delete
    • 传递给delete的指针必须指向动态分配的内存或空指针
    • 释放一块非new分配的内存,或将相同的指针值释放多次,行为是未定义的
    • 通常编译器不能分辨指针指向的是静态还是动态分配的对象,也不能分辨一个指针所指向的内存是否已经被释放了
    • const对象值不能被改变,但本身可以被销毁;释放const对象,delete掉指向它的指针即可
  • 动态对象的生存周期直到被释放时为止
    • 对于由内置指针管理的动态对象,直到被显式释放之前它都是存在的
    • 返回指向动态内存的指针,调用者必须记得释放内存
    • 与类类型不同,内置类型的对象被销毁时什么也不会发生;当一个指针离开作用域时,指向的对象什么也不会发生(须在作用域内delete释放内存);如果指向的是动态内存,内存将不会自动释放(内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在)
  • 动态内存的管理非常容易出错
      1. 忘记delete内存(内存泄漏)
      1. 使用已经释放掉的对象(可在释放内存后将指针置为空)
      1. 同一块内存释放两次(当有两个指针指向相同的动态分配对象)
    • 坚持使用智能指针就可以避免所有这些问题
  • delete之后重置指针值······
    • delete一个指针后,指针值就无效了,但很多机器上指针仍保留着(已经释放了的)动态内存的地址,指针变成了空悬指针,指向一块曾经保存数据对象但现在已经无效的内存的指针
    • 未初始化指针的所有缺点空悬指针也有,避免空悬指针的方法:在指针即将离开作用域之前释放掉它所关联的内存;如需保留指针,可在delete之后将nullptr赋予指针
  • ······这只是提供了有限的保护
    • 动态内存的一个基本问题,可能有多个指针指向形同的内存
    • 在delete内存之后重置指针的方法只对这个指针有效,对其他任何指向(已释放的)内存的指针是没有作用的

12.1.3 shared_ptr和new结合使用

  • 如不初始化一个智能指针,它就会被初始化为空指针;还可以使用new返回的指针来初始化智能指针(接受指针参数的智能指针构造函数是explicit,不能将一个内置指针隐式转换为一个智能指针,应当显式创建shared_ptr)
  • 默认情况下用来初始化智能指针的普通指针必须指向动态内存,智能指针默认使用delete释放它所关联的对象
  • 可以将智能指针指向其他类型的资源的指针上,须提供自己的操作来替代delete
表12.3 定义和改变shared_ptr的其他方法
shared_ptr<T> p(q) p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型
shared_ptr<T> p(u) punique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> p(q, d) p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete
shared_ptr<T> p(p2, d) pshared_ptr p2的拷贝,唯一的区别是p将可调用对象d来代替delete
p.reset() p是唯一指向其对象的shared_ptrreset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,则会调用d而不是delete来释放q
p.reset(q) 同上
p.reset(q, d) 同上
  • 不要混合使用普通指针和指针指针······
    • shared_ptr可以协调对象的析构(推荐使用make_shared而不是new,在分配对象的同时就将shared_ptr与之绑定)
    • 使用内置指针访问一个智能指针所负责的对象是很危险的,无法知道对象何时会被销毁
  • ······也不要使用get初始化另一个智能指针或为智能指针赋值
    • 智能指针类型定义了一个get函数,返回以一个内置指针,指向智能指针管理的对象
    • 需要向不能使用智能指针的代码传递一个内置指针
    • 使用get返回的指针的代码不能delete此指针
    • 将另一个智能指针也绑定到get返回的指针上是错误的(两个独立的shared_ptr指向相同的内存,未定义(空悬指针;二次delete))
    • get用来将指针的访问权限传递给代码,只有确定代码不会delete指针的情况下,才能使用get;永远不要使用get初始化另一个智能指针或为另一个智能指针赋值
  • 其他shared_ptr操作
    • 用reset来将一个新的指针赋予一个shared_ptr
    • reset会更新引用计数,如果需要会释放p指向的对象
    • reset配合unique使用,控制多个shared_ptr共享的对象;在改变底层对象之前,我们检查自己是否是当前对象仅有的对象,如果不是在改变之前要制作一份新的拷贝

12.1.4 智能指针和异常

  • 使用异常处理程序能在异常发生后令程序流程继续
  • 程序需确保在异常发生后资源能被正常的的释放(使用智能指针)
  • 程序的退出(局部对象会被销毁)
    • 正常处理结束
    • 发生了异常
      • 发生异常时,直接使用的内存不会释放(使用内置指针管理内存,new之后在对应delete之前发生了异常则内存不会被释放)
  • 智能指针和哑类
    • 分配了资源而又没有定义析构函数来释放这些资源的类,可以使用shared_ptr
  • 使用我们自己的释放操作
    • 默认情况,shared_ptr管理的是动态内存,当其销毁时默认对管理的指针进行delete操作
    • shared_ptr管理一个connection时,需定义一个函数代替delete(删除器),完成对shared_ptr中保存的指针进行释放的操作(正常退出和异常 连接都可以被关闭)
  • 智能指针陷阱(为正确使用智能,坚持以下规范)
      1. 不使用相同的内置指针初始化(或reset)多个智能指针
      1. 不delete get()返回的指针
      1. 不使用get()初始化或reset另一个智能指针
      1. 若使用get()返回的指针,最后一个对应的指针销毁后,指针就变成了无效的
      1. 使用智能指针管理的资源而不是new分配的内存,记得传递给它一个删除器

12.1.5 unique_ptr

  • 某个时刻只能有一个unique_ptr指向给定对象;
  • 当unique_ptr被销毁时,它所指向的对象也被销毁;
  • unique_ptr没有类似make_shared的标准库返回一个unique_ptr
  • 定义一个unique_ptr时,需将其绑定到一个new返回的指针上,须采用直接初始化
  • unique_ptr不支持普通的拷贝或赋值操作
表12.4 unique_ptr操作
unique_ptr<T> u1 unique_ptr,可以指向类型是T的对象。u1会使用delete来释放它的指针。
unique_ptr<T, D> u2 u2会使用一个类型为D的可调用对象来释放它的指针
unique_ptr<T, D> 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置空
  • 可通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique

    • release成员返回unique_ptr当前保存的指针并将其置为空;调用release会切断unique_ptr和它原来管理对象间的联系;若不用另一个智能指针来保存release返回的指针,程序就要负责资源的释放
    • reset接受一个可选指针参数,令unique_ptr重新指向给定的指针;若unique_ptr不为空,它原来指向的对象被释放
  • 传递unique_ptr参数和返回unique_ptr

    • 不能拷贝unique_ptr的例外
      • 可以拷贝或赋值一个将要被销毁的unique_ptr(如从函数返回的unique_ptr)
  • 向后兼容 auto_ptr

    • 标准库较早版本:auto_ptr类,具有unique_ptr部分特性,但不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr
  • 向unique_ptr传递删除器

    • 可重载一个unique_ptr中默认的删除器,但与share_ptr的方式不同
    • 重载unique_ptr中的删除器会影响到unique_ptr类型以及如何构造(或reset)该类型的对象
    • 须在尖括号中unique_ptr指向类型之后提供删除器类型,在创建或reset一个这种unique_ptr类型的对象时,须提供一个指定类型的可调用对象(删除器)
      • 可使用decltype来指明函数指针类型,decltype(function)返回的是函数类型,需加一个*指出使用的是类型的指针

12.1.6 weak_ptr

  • 不控制所指对象生存期的智能指针,指向一个shared_ptr管理的对象
  • 将一个weak_ptr绑定到share_ptr不会改变shared_ptr的引用计数
  • 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak
    _ptr指向对象
表12.5 weak_ptr
weak_ptr<T> w weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) shared_ptr指向相同对象的weak_ptrT必须能转换为sp指向的类型。
w = p p可以是shared_ptr或一个weak_ptr。赋值后wp共享对象。
w.reset() w置为空。
w.use_count() w共享对象的shared_ptr的数量。
w.expired() w.use_count()为0,返回true,否则返回false
w.lock() 如果expiredtrue,则返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
  • 由于对象可能不存在,不能使用weak_ptr直接访问对象,必须调用lock
  • 此函数检查weak_ptr指向的对象是否存在,若存在返回共享对象shared_ptr
  • 核查指针类
    • weak_ptr不参与与其对应的shared_ptr的引用计数,须检查指针指向的vector是否存在;若vector已销毁,lock将返回一个空指针,抛出异常;否则check检查给定索引是否合法;如索引合法则返回lock获得的shared_ptr
  • 指针操作
    • (*p)[curr] 解引用shared_ptr获得vector,使用下标运算符提取并返回curr位置上的元素
    • 将指针类StrBlobPtr声明为StrBlob的friend,为strblob定义begin和end操作,返回自身的StrBlobPtr

12.2 动态数组

  • 容器需要重新分配内存时,必须一次性为很多元素分配内存
    • c++语言定义了另一种new表达式语法,可以分配并初始化一个对象数组
    • 标准库 allocator的类,允许将分配和初始化分离;会提供更好的性能和更灵活的内存管理能力
    • 使用容器的类可以使用默认版本的拷贝、赋值和析构操作;分配动态数组的类则必须定义自己版本的操作,拷贝、复制、以及销毁对象时所关联的内存

12.2.1 new和数组

  • new分配对象数组,在类型名之后跟一对方括号,指明要分配的对象的数目

  • 也可以用一个表示数组类型的类型别名typedef来分配一个数组

  • 分配一个数组会得到一个元素类型的指针

    • 使用new分配数组时,得到的是一个数组元素类型的指针,指向对象数组首元素
    • 不能对动态数组调用begin或end,也不能用范围for语句
      • 原因:这些函数使用数组维度(维度是数组类型的一部分)来返回指针
  • 初始化动态分配对象的数组

    • new分配的对象(单个/数组)都是默认初始化
    • 可对数组中元素进行值初始化,在大小(中括号)后跟一对空括号(不能在括号中给出初始化器,不能使用auto分配数组)
    • 还可以提供一个初始化器的花括号列表
      • 初始化数组中开始部分的元素
      • 初始化器小于元素数目,剩余元素将进行值初始化
      • 初始化器数目大于元素数目,则new表达式失败,不分配任何内存
  • 动态分配一个空数组是合法的

    • 使用new分配一个大小为0的数组是,new返回一个合法的非空指针(与new返回的其他任何指针都不相同)
    • 类似尾后指针,可用于比较操作,但不能解引用(不指向任何元素)
  • 释放动态数组

    • 特殊形式的delete,在指针前加上一个空的方括号
    • 销毁动态分配的数组中的元素并释放对应的内存;数组元素逆序销毁,最后一个元素首先销毁
    • delete一个指向数组的指针忽略了方括号或delete单一对象时使用了方括号的行为都是未定义的
  • 智能指针和动态数组

    • 标准库 可管理new分配的数组的unique_ptr版本,尖括号中的类型要加空的方括号unique_ptr<int []>up(new int[10])
    • 当unique_ptr指向的是一个数组时,不能使用点和箭头成员运算符,可以使用下标运算符来访问数组中的元素
表12.6 指向数组的unique_ptr
指向数组的unique_ptr不能使用点和箭头成员运算符,其他操作不变
unique_ptr<T[]> u u可以指向一个动态分配的数组,整数元素类型为T
unique_ptr<T[]> u(p) u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*
u[i] 返回u拥有的数组中位置i处的对象。u必须指向一个数组。
  • shared_ptr不直接支持管理动态数组
  • 如使用shared_ptr管理动态数组,须提供自己定义的删除器
    • 可使用lambda表达式释放动态数组{delete [] p;}
    • 原因 默认情况下shared_ptr使用delete销毁它指向的对象,而释放动态数组时需要加方括号delete []
  • shared_ptr未定义下标运算符,且不知处指针的算术运算,为访问数组中的元素,需用get获取一个内置指针来访问数组元素

12.2.2 allocator类

  • new灵活性上的局限,将内存分配和对象构造组合在了一起
  • delete将对象析构和内存释放组合在了一起
  • 分配单个对象时,希望将内存分配和对象初始化组合
  • 分配一大块内存时,计划在内存上按需构造对象,希望内存分配和对象构造分离
  • 没有默认构造函数的类就不能动态分配数组了
  • allocator类(模板类)
    • 将内存分配和对象构造分离
    • 提供一种类型感知的内存分配方法,分配的内存是原始的、未构造的
    • 定义allocator对象,指明对象类型,会根据给定对象类型来确定恰当的内存大小和对齐位置
表12.7 标准库allocator类及其算法
allocator<T> a 定义了一个名为aallocator对象,它可以为类型为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) pT*类型的指针,此算法对p指向的对象执行析构函数。
  • allocator分配未构造的内存
    • allcoator分配的内存是未构造的,需在内存中构造对象
    • construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造元素
      • 额外参数用来初始化对象,须与构造的对象的类型相匹配
      • construct早期版本只接受两个参数
        • 指向创建对象位置的指针
        • 一个元素类型的值
    • 还未构造对象的情况下就使用原始内存是错误的
    • 用完对象后需调用destory销毁
      • destory接受一个指针,对指向的对象调用析构函数
      • 销毁后可以重新使用这部分内存
      • 可以通过deallocate释放内存
        • alloc.deallocate(p,n);
        • 传递给deallocate的指针不能为空,须指向由allcoate分配的内存;传递给deallocate的大小参数须与调用allocated分配内存时提供的大小参数具有一样的值
  • 拷贝和填充未初始化内存的算法
    • allocator类定义两个伴随算法,可在未初始化内存中创建对象
表12.8 allocator算法
uninitialized_copy(b, e, b2) 从迭代器be指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能够容纳输入序列中元素的拷贝
uninitialized_copy_n(b, n, b2) 从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。
uninitialized_fill(b, e, t) 在迭代器be执行的原始内存范围中创建对象,对象的值均为t的拷贝。
uninitialized_fill_n(b, n, t) 从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象
  • 类似copy,uninitialized_copy返回(递增后的)目的位置迭代器,指向最后一个构造的元素之后的位置

12.3 使用标准库:文本查询程序

  • 文本查询程序
    • 用户在一个给定文件中查询单词
    • 查询结果:单词在文件中出现的次数以及其所在行的列表

12.3.1 文本查询程序设计

  • 程序设计:列出程序的操作(分析需要的数据结构)
    • 逐行读取输入文件,将每行分解成独立的文件
    • 输出 单词关联行号;行号升序无重复;打印给定行号文本
    • 利用标准库 实现要求
      • vector 行号为下标
      • istringstream将每行分解为单词
      • set保存单词出现的行号(保证每行只出现一次且升序)
      • map将单词与行号set关联
  • 数据结构
    • 保存输入文件的类 TextQuery
      • vector 保存输入文件的文本
      • map 关联每个单词和它出现行号的set
      • 构造函数 读取给定输入文件
      • 查询操作
        • 查找map成员,检查单词是否出现;返回出现次数/行号/每行的文本
        • 定义另一个类QueryResult保存查询结果,打印结果
    • 在类之间共享数据
      • QueryResult查询结果类,结果数据保存在TextQuery文本对象类里
      • 通过返回指向TextQuery对象内部迭代器/指针,可避免拷贝操作
        • 但若TextQuery对象在对应QueryResult对象之前被销毁,会导致QueryResult引用不再存在的对象
        • QueryResult对象和TextQuery对象的生存周期应该同步,可使用shared_ptr
  • 使用TextQuery类
    • 设计一个类时,在真正实现成员之前,先编写程序使用这个类
      • 可看到类是否具有我们需要的操作

12.3.2 文本查询程序类的定义

  • TextQuery类定义

    • 创建时,提供istream读取输入文件
    • 提供query操作,接受一个string,返回QueryResult表示string的那些行
  • 设计类数据成员时,须考虑对象共享数据需求

    • QueryResult类须共享TextQuery类中输入文件的vector和保存单词关联行号的set
      • 指向vector的shared_ptr
      • 一个键为string值为shared_ptr的map
  • TextQuery构造函数

    • 接受一个ifstream逐行读取输入文件
    • getline逐行读取输入文件,存入vector
    • istringstream处理读入行的每一个单词,将单词存入string word中
    • while循环 map以word为下标 创建一个新的map,键值为shared_ptr lines
    • 对lines是个引用,若lines为空,分配新的set并用reset更新使lines指向新分配的set
    • 将当前行号用insert添加到set中;若一单词在同一行中出现多次则insert什么也不做
  • QueryResult类

    • string 保存查询单词
    • shared_ptr 指向保存输入文件的vector
    • shared_ptr 指向保存单词出现行号的set
    • 唯一成员函数 构造函数
      • 初始化以上三个数据成员
      • 将参数保存在对应的数据成员中
  • query函数

    • 接受一个string(查询单词),通过map的以map为键定位到对应的行号set
    • 如果找到这个string,query函数构造一个QueryResult(保存给定string/TextQuery的file,以及map中对应的set)
    • 若为找到,返回一个空的shared_ptr
  • 打印结果

    • 单词出现次数 set的size
    • 单词出现的行号
    • 以及该行对应的内容

小结

  • c++ new分配内存 delete释放内存
  • allocator分配动态内存
  • 分配动态内存的程序应负责释放它所分配的内存
  • 内存释放出错的情况
    • 内存永远不会被释放
    • 在仍有指针的时候就释放了
  • 智能指针类型 shared_ptr unique_ptr weak_ptr
    • 可令动态内存管理更为安全
    • 一块内存,当没有任何用户使用它时,智能指针自动释放它

术语表

  • allocator 标准类库 用来分配未构造的内存
  • 空悬指针 一个指向曾经保存一个对象但现在已释放的内存的指针
  • 释放器(deleter) 传递给智能指针的函数,用来代替delete释放指针绑定的对象
  • 定位new 一种new表达形式,接受一些额外的参数,在new关键字后面的括号中给出。如neew(nothrow) int告诉new不要抛出异常

猜你喜欢

转载自blog.csdn.net/m0_68312479/article/details/130895946