第12章 动态内存
1. 动态内存的对象的生存期与它们在那里创建是无关的,只有当显示地被释放时,这些对象才被销毁。
2. 程序用堆(或自由空间)来存储动态分配的对象,即那些在程序运行时分配的对象。
3. 动态内存的管理是通过一对运算符来完成的:
new 在动态内存中为对象分配空间并返回一个指向该对象的指针;
delete 接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
3. 动态内存的使用是很容易出现问题的,因为确保在正确的时间释放内存是极其困难的。有时我们忘记释放内存,在这种情况下就会产生内存泄漏;有时在尚有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。
4. 为了更容易的使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。
5. 新标准库提供的两种智能指针的区别在于管理底层指针的方式:
shared_ptr 允许多个指针指向同一个对象
unique_ptr 独占所指向的对象
除此之外还定义了一个weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
这三种类型都定义在头文件memory中。
6. 智能指针也是模板,当创建一个智能指针时,必须提供额外的信息——指针可以指向的类型。
shared_ptr<string> p1;
默认初始化的智能指针中保存着一个空指针。
7. 最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数,返回指向此对象的shared_ptr。
shared_ptr<string> p2 = make_shared<string>( );
或 auto p2 = make_shared<string>( );
调用make_shared<string> 时传递的参数必须与string的某个构造函数相匹配。如果不传递参数,对象将会进行值初始化。
8. 每个shared_ptr都有一个关联的计数器,成为引用次数。无论何时拷贝一个shared_ptr,计数器都会增加。
auto r = make_shared<int>(42); //r 引用为1次
r=q; //r 引用次数为0 ,自动释放
q 的计数器会+1,r 的计数器会-1
9. 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr会通过其成员函数—析构函数来完成销毁工作,析构函数控制此类型的对象销毁时做什么操作。
shared_ptr的析构函数会递减它所指向的对象的引用计数,如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
10. 使用动态内存的三个原因:
(1)程序不知道自己需要使用多少对象(2)程序不知道所需对象的精确类型(3)程序需要在多个对象间共享数据
11. C++定义了两个运算符来分配和释放动态内存,new和delete。相对于智能指针,使用这两个运算符管理内存非常容易出错,所以不建议使用。
12. 在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:
int *pi =new int ; pi指向一个动态分配的,未初始化的无名对象。
此new表达式(step 1)在自由空间中创造一个int型对象,(step 2)并返回指向该对象的指针。
也可以对其进行值初始化,只需要在类型名后添加一对括号: string *pi= new string( ); 当然也可使用auto: auto pi=new string( );
13. 虽然计算机通常都配备大容量的内存,但是自由空间被耗尽的情况还是有可能发生的。为了防止内存耗尽,在动态内存使用完毕后,必须将其归还给系统,通过delete表达式来进行。
delete pi; //pi必须指向一个动态分配的对象或是一个空指针
delete与new类似,同样执行两步操作:step1 销毁给定的指针指向的对象;step 2 释放对应的内存。
释放一块非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的。
14. 通常情况下,编译器不能分辨一个指针指向的是静态还是动态分配的对象。类似的,编译器也不能分辨一个指针所指向的内存是否已经被释放。对于一个内置指针管理的动态对象,直到被显示释放之前都是存在的。因此,返回指向动态内存(而不是智能指针)的函数,调用者必须记得释放内存。忘记释放内存会导致内存泄漏。
15. 当delete一个指针之后,指针值就无效了,但指针任然保存着已经释放了的动态内存的地址。在delete之后,指针就变成了空悬指针,即,指向一块曾经保存数据对象但现在已经无效的内存的指针。避免空悬指针的方法:在指针即将离开其作用域之前释放掉它所关联的内存。
悬垂指针(Dangling pointer)和野指针(Wild pointer)
16. weak_ptr 是一种不控制所指向对象生存期的只能指针,它指向由一个shared_ptr管理的对象。将一个weak_ptr 绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,即使有weak_ptr指向对象,对象也还是会被释放。
17. new和delete一次分配释放一个对象,但如果想要一次性为很多对象分配内存,就需要使用动态数组。可以使用new,也可以使用标准库中包含一个allocator类,其允许我们将分配和初始化分离。
18. 为了让new分配一个对象数组,要在类型名后加一对方括号,指明要分配的对象的数目:
int *pa = new int[42]; string *pi = new string[get_size()];
19. 虽然称new分配的内存成为“动态数组”,但这种叫法某种程度有些误导。当用new分配一个数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。所以分配的内存并不是一个数组类型,因此不能调用begin和end。
20. 初始化动态内存分配对象的数组:
int *pi = new int[10](); //10个值初始化为0的int;
int *pp= new int[10]{1,2,3,4,5,6,7,8,9,0}; //用初始化器的花括号列表来初始化
21. 虽然不能创建一个大小为0的静态数组对象,但是当n=0时,调用new[n]是合法的。new会返回一个合法的非空指针,此指针就像尾后指针一样。但此指针不讷讷个解引用,因为不指向任何元素。
22. 释放动态数组:
delete p; //p必须指向一个动态分配的对象或为空
delete [ ]p; //p必须指向一个动态分配的数组或为空,数组中的元素按逆序销毁
23. new 将内存分配和对象构造组合在了一起
delete 将对象析构和内存释放组合在了一起
标准库allocator类帮助我们将内存分配和对象构造分离开。allocator也是一个模板。