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
分配的内存,或将相同的指针值释放多次的行为是未定义的 -
动态对象的生存期直到被释放时为止
-
与类类型不同,内置类型的对象被销毁时什么也不会发生。特别是当一个指针离开其作用域时,它所指向的对象什么也不会发生,如果指向的是动态内存,那么内存不会被自动释放
-
由内置指针管理的动态内存被显式释放前一直都会存在
-
使用
new
和delete
管理动态内存存在三个常见问题,坚持只用智能指针可以避免这些问题- 忘记
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
**但是可以通过调用release
或reset
将指针的所有权从一个(非const
)unique_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
直接访问对象,而必须调用lock
。lock
检查对象是否存在,如果存在,返回一个指向共享对象的shared_ptr
- 通过使用
weak_ptr
,不会影响一个给定的对象的生存期,但是可以阻止用户访问一个不再存在的对象的企图
12.2 动态数组
- 标准库包含一个名为
allocator
的类,允许我们将分配和初始化分离,使用allocator
通常会提供更好的性能和更灵活的内存管理能力 - 实际上大多数应用没有直接访问动态数组的需求,当一个应用需要可变数量对象时,使用
vector
会更方便,使用容器的类可以使用默认版本的拷贝、赋值和析构操作 - 分配动态数组的类必须定义自己版本的操作,在拷贝、复制以及销毁对象时管理所关联的内存
12.2.1 new 和数组
- 通常称
new T[]
分配的内存为动态数组,但要记住它并不是数组类型,我们得到一个数组元素类型的指针 new int[10]
10个还没初始化的int
,new int[10]()
10个值初始化为0的int
new T[]
分配的内存也不是数组类型,这意味着- 不能对动态数组调用
begin
或end
- 不能用范围
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_copy
和uninitialized_fill
函数可以在给定的目的位置穿件元素,而不是系统分配内存给它们
12.3 使用标准库:文本查询程序
- 如果两个类概念上共享了数据,可以使用
shared_ptr
来反映数据结构中的这种共享关系 - 当我们设计一个类时,真正实现成员之前先编写程序使用这个类,是一种非常有用的方法
以上是关于C++ Prime 0x0C 学习笔记的主要内容,如果未能解决你的问题,请参考以下文章