【C++】《C++ Primer 5th》笔记-Chapter12-动态内存

笔记:
一、动态内存与智能指针
1、静态内存是用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。分配在静态或栈内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。
2、除了静态内存和栈内存,每个程序还拥有一个内存池。这部分内存被称作自由空间或堆。程序用堆来存储动态分配的对象——即,那么在程序运行时分配的对象。动态对象的生存期由程序来控制。
3、新标准提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则"独占"所指向的对象。
标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
这三种类型都定义在memory头文件中。
4、默认初始化的智能指针中保存着一个空指针。
5、解引用一个智能指针返回它所向的对象。
6、最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。与智能指针一样,make_shared也定义在头文件memory中。
我们通常用auto定义一个对象来保存make_shared的结果。
7、当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。
8、我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
例如:
auto r = make_shared<int>(42);
r = q;    // 给r赋值,令它指向另一个地址。
        // 递增q指向的对象的引用计数。
        // 递减r原来指向的对象的引用计数
        // r原来指向的对象已没有引用者,会自动释放

9、如果你将shared_ptr存在于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。
10、用new和delete自己直接管理内存的类与使用智能指针的类不同,它们不能依赖类对象拷贝、赋值和销毁操作的任何默认定义。
11、在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
12、默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化。
13、对于定义了自己的构造函数的类类型(例如string)来说,要求值初始化是没有意义的;不管采用什么形式,对象都会通过默认构造函数来初始化。但对于内置类型,两种形式的差别就很大了;值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。
14、用new分配const对象是合法的:
// 分配并初始化一个const int
const int *pci = new const int(1024);
类似于其他任何const对象,一个动态分配的const对象必须进行初始化。

15、默认情况下,如果new不能分配所要求的内存空间,它会抛出一个类型bad_alloc的异常。
我们可以改变使用new的方式来阻止它抛出异常:
int *p1 = new int;                // 如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int;    // 如果分配失败,new返回一个空指针
这种形式的new称为定位new。
nothrow传递给new,意图是告诉它不能抛出异常。

16、delete p;    // p必须指向一个动态分配的对象或是一个空指针
释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的。
所以delete p; p = nullptr; 一起使用效果更佳.

18、由内置指针(而不是智能指针)管理的动态内存在被显式释放前一直都会存在。
19、坚持只使用智能指针就可以避免忘记delete内存、使用已经释放掉的对象、同一块内存释放两次等问题。对于一块内存,只有在没有任何智能指针指向它的情况下,智能指针才会自动释放它。
20、可以用new返回的指针来初始化智能指针,接收指针参数的智能指针构造函数是explicit的。
因此,我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针:
shared_ptr<int> p1 = new int(1024);    // 错误:必须使用直接初始化形式
shared_ptr<int> p2(new int(1024));    // 正确:使用了直接初始化形式

21、shared_ptr可以协调对象的析构,但这仅限于其自身的拷贝(也是shared_ptr)之间。这也是为什么我们推荐使用make_shared而不是new的原因。这样,我们就能在分配对象的同时就将shared_ptr与之绑定,从而避免了无意中将同一块内存绑定到多个独立创建的shared_ptr上。

22、当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。因为我们无法知道对象何时会被销毁。

23、智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。
使用get返回的指针的代码不能delete此指针。
永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。

24、可以用reset来将一个新的指针赋予一个shared_ptr:
p = new int(1024);        // 错误:不能将一个指针赋予shared_ptr
p.reset(new int(1024));    // 正确:p指向一个新对象

25、与赋值类似,reset会更新引用计数,如果需要的话,会释放p指向的对象。reset成员经常与unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们坚持自己是否是当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝:
if(!p.unique())
    p.reset(new string(*p));    // 我们不是唯一用户;分配新的拷贝
*p += newVal;        // 现在我们知道自己是唯一的用户,可以改变对象的值

26、如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。
与之相对的是,当发生异常时,我们直接管理的内存是不会自动释放的。如果在new和delete之间发生异常,且异常未被捕获到,则内存就永远不会被释放了。

27、使用智能指针的一些基本规范:
①不使用相同的内置指针值初始化(或reset)多个智能指针。
②不delete get()返回的指针。
③不使用get()初始化或reset另一个智能指针。
④如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了。
⑤如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

28、一个unique_ptr"拥有"它所指向的对象。与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,它所指向的对象也被销毁。

29、与shared_ptr不同,没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。

30、由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作。
虽然我们不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个(非const)unique_ptr转移给另一个unique。
release成员返回unique_ptr当前保存的指针并将其置为空。
reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。如果unique_ptr不为空,它原来指向的对象被释放。
注意,如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放。
p2.release();            // 错误:p2不会释放内存,而且我们丢失了指针
auto p = p2.release();    // 正确,但我们必须记得delete(p)

31、不能拷贝unique_ptr的规则有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr,最常见的例子是从函数返回一个unique_ptr。

32、weak_ptr是一种不控制所指向对象生存期的智能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。

33、由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。此函数检查weak_ptr指向的对象是否存在。如果存在,lock返回一个指向共享对象的shared_ptr。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);
if(shared_ptr<int> np = wp.lock())    //
{ // 在if中,np与p共享对象 }

二、动态数组
1、大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。
2、注意,动态数组并不是数组类型。
3、虽然我们不能创建一个大小为0的静态数组对象,但当n等于0时,调用new[n]是合法的:
char arr[0];            // 错误:不能定义长度为0的数组
char *cp = new char[0];    // 正确:但cp不能解引用
当我们用new分配一个大小为0的数组时,new返回一个合法的非空指针。此指针保证与new返回的其他任何指针都不相同。对于零长度的数组来说,此指针就像尾后指针一样,我们可以像使用尾后迭代器一样使用这个指针。

4、为了释放动态数组,我们使用一种特殊形式的delete——在指针前加上一个空方括号对:
delete [] pa;    // pa必须指向一个动态分配的数组或为空
数组中的元素按逆序销毁,即,最后一个元素首先被销毁,然后是倒数第二个,依此类推。
如果我们在delete一个指向数组的指针时忽略了方括号(或者在delete一个指向单一对象的指针时使用了方括号),其行为是未定义的。

5、标准化提供了一个可以管理new分配的数组的unique_ptr版本。
unique_ptr<int[]> up(new int[10]);
up.release();    // 自动用delete[]销毁其指针

6、与unique_ptr不同,shared_ptr不直接支持管理动态库数组。如果希望使用shared_ptr关联一个动态数组,必须提供自己定义的删除器:
// 为了使用shared_ptr,必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p) { delete [] p; });
sp.reset();    // 使用我们提供的lambda释放数组,它使用delete[]

7、当一个unique_ptr指向一个数组时,我们可以使用下标运算符来访问数组中的元素。shared_ptr未定义下标运算符,而且智能指针类型不支持指针的算术运算。因此,为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素。

8、没有默认构造函数的类不能动态分配数组。
9、标准库定义了一个allocator类来分配动态内存块。allocator分配的内存是未构造的。
为了使用allocate返回的内存,我们必须用construct构造对象。使用未构造的内存,其行为是未定义的。
10、当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。而且,我们只能对真正构造了的元素进行destroy操作。
一旦元素被销毁后,就可以重新使用这部分内存来保存其他string,也可以将其归还给系统。

11、释放内存通过调用deallocate来完成。
我们传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。而且传递给deallocate的大小参数必须与调用allocated分配内存时提供的大小参数具有一样的值。

12、标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象。

三、使用标准库:文本查询程序
1、当两个类存在"共享"数据时,可以使用shared_ptr来反映数据结构中的这种共享关系。

一些术语:
1、释放器:传递给智能指针的函数,用来代替delete释放指针绑定的对象。
2、自由空间:程序可用的内存池,保存动态分配的对象。
3、堆:自由空间的同义词。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值