C++ Prime 0x0C 学习笔记

Posted 鱼竿钓鱼干

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ Prime 0x0C 学习笔记相关的知识,希望对你有一定的参考价值。

📔 C++ Prime 0x0C 学习笔记

更好的阅读体验(实时更新与修正)

推荐阅读 《C++ Primer 5th》知识点总结&练习题解

12.1 动态内存与智能指针

  • 运算符new,在动态内存中为对象分配空间并返回一个指向该对象的指针,我们可以选择对对象初始化
  • 运算符delete,接受一个动态对象的指针,销毁该对象,并释放与之关联的内存
  • 智能指针负责自动释放所指向的对象,定义在memory头文件中
    • shared_ptr允许多个指针指向同一个对象
    • unique_ptr独占所指向的对象
    • weak_ptr是一种弱引用,指向shared_ptr所管理的对象

12.1.1 shared_ptr 类

  • 智能指针也是模板

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

  • 当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象,可以认为关联了一个计数器(通常称引用计数,具体用什么数据结构实现,完全由标准库的具体实现来决定)

  • 当我们给shared_ptr赋予一个新值或是shared_ptr被销毁,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象

  • 当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动通过析构函数销毁此对象

  • shared_ptr还会自动释放相关联的内存

  • 如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,只用部分,记得要用erase删除不再需要的元素

  • 程序使用动态内存出于三种原因之一

    • 程序不知道自己需要使用多少对象
    • 程序不知道所需对象的准确类型
    • 程序不知道需要在多少个对象之间共享数据,使用动态内存的一个常见原因是允许多个对象共享相同状态
  • 如果两个对象共享底层数据,当某个对象被销毁是,我们不能单方面地销毁底层数据

12.1.2 直接管理内存

  • 在自由空间分配的内存是无名的,因此new无法为其分配的对象明明,而是非那会一个指向该对象的指针

  • 默认情况下,动态分配的对象是默认初始化的,我们也可以进行值初始化

  • new分配const对象是合法的,返回的指针是一个指向const的指针,一个动态分配的const对象必须初始化

  • 默认情况下,new不能分配所要求内存空间,会跑出一个类型为bad_alloc的异常。我们也可以改变使用new的方式阻止跑出异常(定位new)

    int *p2 = new (nothrow) int;//如果分配失败,返回空指针
    
  • delete执行两个动作:销毁给定指针指向的对象,释放对应的内存

  • 我们传递给delete的指针必须指向动态分配的内存或是一个空指针,释放一个并非new分配的内存,或将相同的指针值释放多次的行为是未定义的

  • 动态对象的生存期直到被释放时为止

  • 与类类型不同,内置类型的对象被销毁时什么也不会发生。特别是当一个指针离开其作用域时,它所指向的对象什么也不会发生,如果指向的是动态内存,那么内存不会被自动释放

  • 由内置指针管理的动态内存被显式释放前一直都会存在

  • 使用newdelete管理动态内存存在三个常见问题,坚持只用智能指针可以避免这些问题

    • 忘记delete内存
    • 使用已经释放掉的对象
    • 对同一块内存释放两次
  • delete一个指针后,指针值就无效了。虽然指针已经无效,但是很多机器上指针仍然保存着已经释放了动态内存的地址。在delete之后,指针变成空悬指针,指向一块曾经保存对象但现在已经无效的内存的指针。所以要在delete后将nullptr赋予指针。(但这只是有限的保护了这一个指针,不能影响其他指向该内存的指针)

12.1.3 shared_ptr 和 new 结合使用

  • 如果我们不初始化一个智能指针,它就会被初始化为一个空指针

  • 可以用new返回的指针来初始化智能指针,接受指针参数的智能指针构造函数是explicit的,因此不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针(但不建议使用,最好用make_shared)

  • 默认情况下一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认用delete释放对象

  • 不要混合使用普通指针和智能指针,shared_ptr可以协调对象的析构,但这仅限其自身的拷贝(也是shared_ptr)之间。所以推荐使用make_shared,而不是new。这样,我们就能在分配对象的同时就将shared_ptr与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr

  • 智能指针类型定义了一个名为get的函数,返回一个内置指针,指向智能指针管理的对象。用来解决这样的情况:我们需要向不能使用智能指针的代码传递一个内置指针

  • get返回的指针的代码不能delete此指针

  • 不要使用get初始化另一个智能指针或为智能指针赋值

  • reset将一个新的指针赋予一个shared_ptr,会更新引用计数

  • unique检查自己是否是当前对象的仅有用户

12.1.4 智能指针和异常

  • 当发生异常时,我们直接管理的内存是不会自动释放的。如果使用内置指针管理内存,且在new之后对应的delete之前发生了异常,则内存不会被释放掉
  • 如果用shared_ptr来管理不是动态内存的资源,我们需要定义一个函数来代替delete
  • 智能指针使用规范
    • 不使用相同的内置指针初始化(或reset)多个智能指针
    • delete通过get()返回的指针
    • 不使用get()初始化或reset另一个智能指针
    • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就无效了
    • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器

12.1.5 unique_ptr

  • 一个unique_ptr拥有它所指向的对象,某个时刻只能有一个unique_ptr指向一个给定对象,当unique_ptr被销毁时,它所指向的对象也被销毁
  • unique_ptr没有类似make_shared的标准库函数,当我们定义一个unique_ptr时,需要绑定到一个new返回的指针上且必须采用直接初始化的方式进行初始化
  • **我们不能拷贝或赋值unique_ptr**但是可以通过调用releasereset将指针的所有权从一个(非constunique_ptr转移给另一个
  • 较早标准库包含了一个名为auto_ptr的类,具有unique_ptr的部分特性但不是全部。我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr
  • u.release()u放弃对指针的控制权,返回指针,并将u置为空
  • u.reset(q):接受一个可选的指针参数,令unique_ptr重新指向给定的指针,如果unique_ptr原来不为空,它原来指向的对象被释放
  • 不能拷贝unique_ptr规则有个例外,我们可以拷贝或赋值一个将要被销毁的unique_ptr,最常见的就是从函数返回一个unique_ptr
  • unique_ptr默认情况下使用delete释放它指向的对象,我们可以传递一个删除器。但是unique_ptr管理删除器的方式和shared_ptr不一样。
  • 重载一个unique_ptr中的删除器会影响到unique_ptr类型以及如何构造(或reset)该类型的对象

12.1.6 weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针,它指向一个shared_ptr管理的对象
  • 将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。如果最后一个shared_ptr被释放销毁了,即使有weak_ptr,对象也会被释放。这就是weak,弱共享对象。
  • 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用locklock检查对象是否存在,如果存在,返回一个指向共享对象的shared_ptr
  • 通过使用weak_ptr,不会影响一个给定的对象的生存期,但是可以阻止用户访问一个不再存在的对象的企图

12.2 动态数组

  • 标准库包含一个名为allocator的类,允许我们将分配和初始化分离,使用allocator通常会提供更好的性能和更灵活的内存管理能力
  • 实际上大多数应用没有直接访问动态数组的需求,当一个应用需要可变数量对象时,使用vector会更方便,使用容器的类可以使用默认版本的拷贝、赋值和析构操作
  • 分配动态数组的类必须定义自己版本的操作,在拷贝、复制以及销毁对象时管理所关联的内存

12.2.1 new 和数组

  • 通常称new T[]分配的内存为动态数组,但要记住它并不是数组类型,我们得到一个数组元素类型的指针
  • new int[10]10个还没初始化的intnew int[10]()10个值初始化为0的int
  • new T[]分配的内存也不是数组类型,这意味着
    • 不能对动态数组调用beginend
    • 不能用范围for语句来处理其中的元素
  • 可以采用int* pia2 = new int[10]();10个值初始化为0的int 。这样空括号对的方式进行值初始化,但不能在括号中给出初始化器,这意味着不能用auto分配数组
  • 动态分配一个空数组是合法的,返回一个合法的非空指针。此指针保证与new返回的其他任何指针不同,可以在此指针上加减0,也可以减去自身得到0,但不能解引用
  • 使用delete []pa;释放动态数组
  • 可以使用unique_ptr来管理new分配的数组,当unique_ptr销毁它管理的指针时,会自动使用delete[]shared_ptr不直接支持管理动态数组,需要自定义删除器,而且也没支持下标和指针算术运算,需要通过get()获取一个内置指针来访问
  • 当一个unique_ptr指向一个数组时
    • 不能用点和箭头运算符,因为是数组而不是单个对象
    • 可以用下标运算符来访问数组中的元素

12.2.2 allocator 类

  • new将内存分配和对象构造组合到一起,delete将对象析构和内存释放组合到一起。这是灵活性上的一些局限,可能会导致一些不必要的浪费
  • allocator类帮助我们将内存分配和对象构造分离开来。提供一种类型感知的内存分配方法,它分配的内存是原始的,未构造的。我们按需在此内存中构造对象。
  • allocator是一个模板
  • 为了使用allocate返回的内存,我们必须用construct构造对象。使用未构造的内存是未定义行为
  • 只能对真正构造了的元素进行destroy操作,来销毁元素
  • 元素被销毁后,可以重新使用这些内存保存其他东西,也可以将其归还给系统。释放内存通过调用deallocate来完成
  • 通过uninitialized_copyuninitialized_fill函数可以在给定的目的位置穿件元素,而不是系统分配内存给它们

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

  • 如果两个类概念上共享了数据,可以使用shared_ptr来反映数据结构中的这种共享关系
  • 当我们设计一个类时,真正实现成员之前先编写程序使用这个类,是一种非常有用的方法

以上是关于C++ Prime 0x0C 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

C++ Prime 0x0A 学习笔记

C++ Prime(第五版)--第二章学习记录

C++ & Linux后端进BAT的学习路线

为啥 0x40010800 + 0x0C 等于 0x40010830?

C++ Prime 0x0A 练习题解

[Lucas定理]学习笔记