智能指针

芝士wa
2024.4.8
参考视频链接


概述

C++的指针包括两种:

  • 原始指针
  • 智能指针:unique_ptr,shared_ptr,weak_ptr

智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄漏。

各种指针中,最常用的是裸指针,其次是unique_ptr和shared_ptr

weak_ptr是shared_ptr的一个补充,应用场景较少

智能指针只解决一部分问题,即独占/共享所有权指针的释放、传输


auto_ptr被C++抛弃的主要原因

  1. 复制或者赋值都会改变资源的所有权

     auto_ptr<string> p1(new string("a"));
     auto_ptr<string> p2(new string("b"));
     
     p1 = p2;
    

    当p2赋值给p1后,首先p1会将自己原先托管的指针释放掉,然后接受p2所托管的指针,p2指向NULL。

  2. 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值

  3. 不支持对象数组的内存管理

    auto_ptr<int[]> array(new int[5]);//不能这样定义


unique_ptr

  • 在任何时刻,只能有一个指针管理内存,两个unique_ptr不能指向同一个资源
  • 当指针超过作用域时,内存将自动释放
  • 该类型指针不可copy,只可以move
  • 在容器中保存指针是安全的

三种创建方式

  • 通过已有裸指针创建
  • 通过new来创建
  • 通过std::make_unique创建(推荐)

创建一个空的unique_ptr对象:

  std::unique_ptr<int> ptr;//空指针

使用原始指针创建,将一个new操作符返回的指针传递给unique_ptr的构造函数,

  std::unique_ptr<class> ptr(new class());
  std::unique_ptr<class> ptr = std::unique_ptr<class>(new class());
  //以上两句代码是等价的,第一句使用了直接初始化,第二句创建了一个临时对象,然后将该临时对象复制给ptr

不能通过赋值的方法创建对象,下面的这句是错误的

  std::unique_ptr<class> ptr = new class();//error

C++14引入了std::make_unique创建unique_ptr对象,可以使用std:::make_unique()函数创建

  std::unique_ptr<class> ptr = std::make_unique<class>();

推荐使用make_unique方法。

unique_ptr的操作

  • 使用get()函数获取管理对象的指针
   class *p1 = ptr.get();//get()会返回地址
  • 重置对象
    在unique_ptr对象上调用reset()函数将重置它,释放原始指针并置空
  ptr.reset();

检查unique_ptr对象是否为空

 // 方法1
    if(!ptr)
    	std::cout<<"ptr is empty"<<std::endl;
    // 方法2
    if(ptr == nullptr)
    	std::cout<<"ptr is empty"<<std::endl;

无法进行复制构造和赋值操作

 int main() 
  {
     // 创建一个unique_ptr实例
      unique_ptr<int> pInt(new int(5));
      unique_ptr<int> pInt2(pInt);    // 报错
      unique_ptr<int> pInt3 = pInt;   // 报错
  }
  1. 使用move函数移交所有权
   std::unique_ptr<class> p1 = std::make_unique<class>();
    std::unique_ptr<class> p2 = std::make_unique<class>();
    p2 = p1.move();//此时p1置空,p2获得p1原先指向对象的所有权。
  1. unique_ptr可以作为函数返回值

  2. 释放关联的原始指针
    在 unique_ptr 对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。


shared_ptr

采用引用计数的办法,可以记录引用特定内存对象的智能指针数量,当复制或拷贝时,引用计数加1,当智能指针析构时,引用计数减1,如果计数为零,代表已经没有指针指向这块内存,那么我们就释放它。这就是shared_ptr采用的策略。

创建方法

  1. 默认构造函数
 std::shared_ptr<T> ptr;//空指针
  1. 使用裸指针进行构造
  std::shared_ptr<T> ptr(new T);
  1. 拷贝构造
   std::shared_ptr<T> ptr1;
   std::shared_ptr<T> ptr2(ptr1);//两个指针指向相同的资源,共同管理其生命周期
  1. 使用自定义删除器进行构造
 std::shared_ptr<T> ptr(new T, deleter);//删除器是一个函数或函数对象,用于在 std::shared_ptr 对象释放资源时执行自定义的清理操作。
  1. 使用自定义删除器和分配器进行构造
 std::shared_ptr<T> ptr(new T, deleter, allocator);//配器用于在 std::shared_ptr 对象需要重新分配内存时进行内存分配操作。
  1. 推荐使用make_shared方法
  std::shared_ptr<int> ptr = std::make_shared<int>(42);

通过 std::make_shared 创建的 std::shared_ptr 具有以下优点:

  • 内存分配是一次性完成的,同时包含对象和控制块,减少了内存开销。
  • 更安全,避免了裸指针的使用,因为 std::make_shared 会处理资源的释放。
  • 更高效,因为 std::make_shared 可以对内存进行更优化的管理。

shared_ptr操作

  1. 引用计数
    调用use_count函数可以获得当前托管指针的引用计数

  2. 管理动态数组

  std::shared_ptr<int[]> ptr(new int[5]);
  1. 主动释放对象
      shared_ptrr<int> up1(new int(10));
      up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放

  1. 重置
      p.reset() ; //将p重置为空指针,所管理对象引用计数减1
      p.reset(p1); //将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
      p.reset(p1,d); //将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器

  1. 交换
      std::swap(p1,p2); // 交换p1 和p2 管理的对象,原对象的引用计数不变
      p1.swap(p2);    // 交换p1 和p2 管理的对象,原对象的引用计数不变

weak_ptr

  1. weak_ptr指针通常不单独使用,只能和 shared_ptr 类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
  2. weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。

构造方法

  shared_ptr<class T> sp;
  weak_ptr wp;     //定义为空的弱指针
  weak_ptr wp2(sp);//使用共享指针构造
  wp2 = sp;        //允许共享指针赋值给弱指针

操作方法

  • 弱指针也可以获得引用计数,但不能改变引用计数
  wp.use_count();

  • 弱指针不支持*和->对指针的访问

  • 可以转换成共享指针lock()
    lock()函数:如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

  shared_ptr<class T> sp_T;
  sp_T = wp.lock();

  sp_T = nullptr;
  • 重置
    reset()函数:将当前 weak_ptr 指针置为空指针。

  • expired()

判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)

死锁问题

如果有两个shared_ptr相互引用,那么这两个shared_ptr指针的引用计数永远不会下降为0,资源永远不会释放。
举例:

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
    {
        cout << "A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout << "B delete\n";
    }
};

void fun() {
    shared_ptr<B> pb(new B());
    cout << "pb.use_count " << pb.use_count() << endl;//1
    shared_ptr<A> pa(new A());
    cout << "pa.use_count " << pa.use_count() << endl;//1

    pb->pa_ = pa;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2
    pa->pb_ = pb;
    //由于share_ptr是共享资源,所以pb所指向的资源的引用计数也会加1
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2
}//程序结束时,没有调用A和B的析构函数

int main()
{
    fun();
    system("pause");
    return 0;
}

为了解决死锁问题,采用weak_ptr,把A中的shared_ptr pb_ 改为weak_ptr pb_weak,传递时不会增加计数,最终能使资源正常释放。

posted @ 2024-04-18 14:55  芝士wa  阅读(67)  评论(0)    收藏  举报