6.cpp智能指针

22 阅读3分钟

智能指针

本质是堆指针的栈对象封装,借助栈对象自动调用析构函数的特征,在析构函数中释放堆内存以实现自动资源释放的特性

1. 不带引用计数的智能指针

  • auto_ptr:拷贝会转移所有权,后续使用被拷贝的智能指针对象会导致行为未定义,且可能导致多个智能指针指向同一个堆内存,导致最后析构多次而程序出错
  • scoped_ptr:删除了拷贝赋值(避免上面问题),但是没有增加移动,无法适用于函数之间的传递
  • unique_ptr:增加了移动拷贝赋值,用户能使用std::move来显式移动资源,且可以使用函数返回直接赋值(临时变量)

2. 带引用计数的智能指针

  • shared_ptr:增加了引用计数,每次拷贝、赋值时会增加引用,引用为0时才会释放代理的堆内存 image.png
  1. 这里引用计数,要使用new RefCount,而不是使用静态变量,原因如下:

  • 静态变量是对类而言的,所有的类shared_ptr<char>都会共享,显然不对,应该做到每个堆内存独一份

  • 共享指针要一般可作用于多线程,在更改引用时要同步,动态分配的计数器可以通过原子操作实现线程安全;而静态变量需要全局锁

  1. 循环引用问题

  • 堆内存上shared对象互相引用 image.png
  • weak_ptr:只是不增加uses引用,但是会增加weaks引用,注意无法访问代理的堆内存,要想访问就必须通过lock()成员方法升级为强指针,当然如果uses==0转换失败

lock()方法返回:如果弱指针指向的内容已被析构,则返回nullptr,否则返回shared_ptr

3. 删除器

  • 由于传入的对象可能时使用new、new[]或者FILE,智能指针在析构时所调用的释放内存函数对象也应该有所不同,此时就需要我们手动传递该函数(即删除器)
  1. 默认的删除器如下,析构中调用的deletor就是Deletor类的实例化对象,所以如果传入的堆对象是用new分配的,则直接使用默认删除器的即可 image.png 2 自定义删除器
  • 法一:仅传递类型进去,由智能指针内部进行实例化 image.png
  • 法二:传递类型以及函数对象 image.png

4. 多线程访问共享对象的线程安全问题

在线程使用共享对象(堆上对象)的时候,应该先确定是否被析构(因为其他线程可析构该对象),如果被析构则无法访问

方法一

  1. 共享对象包装进shared_ptr
  2. 参数传递时强制转换为weak_ptr
  3. 线程使用weak_ptr接受共享对象
  4. 线程使用前先lock()转换为shared,再判断能否使用

方法二

  1. 共享对象包装进shared_ptr
  2. 参数传递依然使用shared_ptr,导致引用+1,这样其他线程不会析构该对象

5. make_shared

  1. 使用 auto sp = std::make_shared<Widget>(arg); // 创建一个shared_ptr指向Widget(arg)对象

  2. 出现原因

  • 使用shared构造函数,内部会有两次内存开辟(数据块和控制块),如果控制块创建失败,则会出现数据块内存泄漏,make_share会只分配一次,将上面两块直接放一起
  • image.png
  1. 优点
  • 内存分配效率高了
  • 防止资源泄漏的风险
  1. 缺点
  • 无法自定义删除器
  • 托管的资源延迟释放,因为weaks也在一块内存上,如果存在弱指针,则不允许将这块资源释放
  1. 补充
  • c++14出现了make_unique,这是将内存分配和unique构造函数调用组合成了原子操作,使得更加安全