智能指针(Smart Pointer)
智能指针(Smart Pointer)
普通指针:指向内存区域的地址变量
使用普通指针容易出现的一些程序的错误
1. 内存泄漏(Memory Leak)
如果一个指针所指向的内存是动态分配的,那么即使这个指针变量离开了所在的作用域,这块内存也不会被 自动销毁
动态分配的内存不进行释放,则会导致内存的泄露
2. 悬空指针(Dangling Pointer)
如果一个指针指向的是一个已经被释放的内存区域,那么这个指针就是悬空指针,使用悬空指针会造成不可预料的结果
3. 野指针(Wild Pointer)===> segmentation fault
定义了一个指针,却未初始化使其指向有效的内存区域时,这个指针就成了野指针。使用野指针访问内存,一般会造成 segmentation fault 错误
使用智能指针可以有效地避免上述错误的发生
由于智能指针是一个对象,它封装了一个指向另外一个对象的指针。当智能指针对象离开作用域后,会被自动销毁,销毁过程中会调用析构函数来删除所封装的对象
1. unique_ptr
以下是 unique_ptr 的常用函数
一对一的关系
创建一个 unique_ptr 对象的方法
例子:
unique_ptr 删除所管理的对象
- 离开作用域
- 赋值语句
- reset 函数
由于 unique_ptr 对所管理的资源具有独占性(不能被拷贝,也不能被赋值)
拷贝报错
赋值报错
但是可以对 unique_ptr 所管理对象的所有权进行转移
而 p1 现在只包含了一个空指针
unique_ptr 对资源的拥有和转移
直接传值是不行的,因为没有相应的拷贝构造函数
因此需要使用 move 函数,将所有全转移给新的 unique_ptr 对象
输出结果:
C++11 推荐:unique_ptr 代替:普通指针
unique_ptr 使用场景(主要还是用在适合使用普通指针的地方)
例如:使用在容器上
定义了一个 Packet 类(一个占用内存较大的对象),使用容器 vector 来装 n 个这样的 Packet 对象,然后对它进行一个排序
由于容器中包含的是对象,对于这种较大的对象,排序意味着:大量数据的移动复制,这样开销会比较大
- 如果将容器中的对象改成指针(则排序时,仅涉及到指针值的复制,那么效率会高很多)
缺点:需要专门的代码对指针进行维护,当删除替换时,需要释放不再使用的指针对象,如果出现异常,提前返回的情况,容易造成内存泄漏 - 而如果将容器中的指针替换成 unique_ptr(不仅获得了接近普通指针的性能,还实现了内存资源的自动释放),不会出现意外的内存泄漏的情况
对 3 种情况进行测试:(按照成员 id 的值进行比较)
AutoTimer 是一个自动计时器:创建对象时,开始计时,销毁对象后停止计时,打印出结果
sortValueVector:对包含 Packet 对象的向量进行排序
AutoTimer 对象,可以实现对所在大括号内语句的执行时间的记录。
同理:对包含 Packet 指针的向量进行的排序
同理:对包含智能指针的向量进行的排序
主程序中,对 1 万个元素的向量进行排序的测试:(可以使用谷歌 Benchark 这样的库来完成)
简化的 unique_ptr(一般情况下,unique_ptr中的真正的数据成员只有一个,就是指向 所管理对象 的指针)
因此,一个 unique_ptr 的智能指针对象的大小与普通指针是相同的
而 unique_ptr 中的成员函数,所带来的额外开销也很小,所以在 C++11后,推荐尽可能使用 unique_ptr 来代替普通指针,这对于实现 RAII【资源获取及初始化的机制】 也是很重要的
2. shared_ptr(通过共同的引用计数器来管理指针)
与 unique_ptr 不同,多个 shared_ptr 对象可以共同管理同一个指针(它们通过一个共同的引用计数器来管理指针)
当其中的一个智能指针对象被销毁时,引用计数器 - 1
当计数器为 0 时,会将指向的内存对象释放
与 unique_ptr 类似,shared_ptr 也提供下列这些常用的函数(除了这个 release 函数之外)
另外,使用 use_count 函数可以获得有多少个 shared_ptr 在共同管理同一个对象
unique 函数则是返回 use_count 是否等于 1(即:当前的 shared_ptr 指针是否是单独管理这个对象的)
举例:
shared_ptr 提供了几个辅助函数
用于对封装指针类型的:
- 静态转换
- 动态转换
- 常量转换
虽然 sp1 和 sp2 的类型不同,但是它们封装的都是指向同一个对象的指针,所以最终我们用 sp1 调用 use_count 函数,输出的引用计数是 3
注意:如果类型转换失败,则返回的共享指针的内部是个空指针
在使用 shared_ptr 容易出现的问题(出现循环引用时,无法释放所指向的资源,造成内存的泄漏)
在主函数中,定义了一个向量 People,用于存放包含 People 指针的 shared_ptr
3. weak_ptr(不能单独使用,需要结合 shared_ptr 来使用)
weak_ptr 的主要特征是:只对 shared_ptr 所管理的对象进行 观测,也就是不会改变对象的引用计数