• C++智能指针之unique_ptr



    前言

      在C++中,动态内存的申请和释放是通过运算符:new 和 delete 进行管理的。其中 new 负责申请内存,delete负责释放内存。

      动态内存的使用很容易出现问题,这主要在于你需要保证在正确的时间释放内存,这是比较困难的,如果你忘记释放内存,就会造成内存泄露;有时在还有指针引用内存的情况下我们就释放了它,在这种情况下就会产生引用非法内存的指针。

      为了更容易(同时也更安全)地使用动态内存,新的标准库提供了两种智能指针类型来管理动态对象,智能指针的行为类似普通指针,最主要的区别在于它负责自动释放所指向的对象。这两种智能指针都定义在 memory 头文件内。

    • 本文是对于unique_ptr的分析。

    一、unique_ptr

    &emsp: 一个 unique_ptr“拥有”它所指向的对象,与shared_ptr不同,某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr 被销毁时,它所指向的对象也被销毁。

    • unique_ptr的操作
      在这里插入图片描述
      在这里插入图片描述

    1.1 unique_ptr类的初始化

      unique_ptr没有类似shared_ptr中make_shared的标准库函数返回一个unique_ptr,我们定义一个unique_ptr时,需要将它绑定到一个new返回的指针上,并且不能直接将new的结果用赋值运算符“=”赋值给unique_ptr(即初始化方式必须采用直接初始化方式)。

    unique_ptr<double> p1;//正确
    unique_ptr<int> p2(new int(42));//正确
    unique_ptr<int> p3 = new int(42);//错误
    
    • 1
    • 2
    • 3

    1.2 unique_ptr禁止拷贝和赋值

      因为unique_ptr所指向的对象只能有一个unique_ptr指针,也就是一个引用计数。因此unique_ptr不支持普通的拷贝和赋值操作

    unique_ptr<string> p1(new string("HelloWorld"));
    unique_ptr<string> p2(p1);//是错误
    unique_ptr<string> p3;
    p3 = p1;//错误
    
    • 1
    • 2
    • 3
    • 4

    特殊情况:

    虽然两个unique_ptr不可以同时指向同一个内存对象,但是可以将一个即将销毁的unqie_ptr指针拷贝或赋值给另一个unqie_ptr

      函数的参数传递和返回值就是一个很好的例子,因为在函数内部的unique_ptr指针随着作用域的结束会自动销毁,因此可以将其作为返回值,然后将内存传递给另一个unique_ptr指针管理。

    unique_ptr<int> clone(int p)
    {
        return unique_ptr<int>(new int(p));
    }
     
    /*unique_ptr clone(int p)
    {
        unique_ptr ret(new int(p));
        return ret;
    }*/
    int main()
    {
        unique_ptr<int> p = clone(10);
        cout <<*p << endl; //打印10
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述

    1.3 release、reset函数

      虽然unique_ptr之间不能拷贝与赋值。但是可以使用release和reset函数来将指针的所有权从一个(非const)unique_ptr转移给另一个unique。

    release函数

    • 将当前的unique_ptr指针所指的内存置为空,并且对这块内存的所有权消失

    • 返回值:返回当前unique_ptr所指的内存

    unique_ptr<string> p1(new string("Hello"));
     
    unique_ptr<string> p2(p1.release());//p1将自己所指的内存空间置空,并且返回该内存空间。之后对该内存空间的操作权消失,从而p2得到该内存的权限
    
    • 1
    • 2
    • 3

    注意事项:

      因为release函数会使unque_ptr指针与内存之间的关系。所以unique_ptr调用release函数之后必须将返回值传递给另一个unqiue_ptr,否则就会内存泄露

    unique_ptr<string> p1(new string("Hello"));
    p1.release();//错误,虽然p1断开了与内存的关系,但是没有另一个unqieu_ptr来接手这块内存,造成内存泄漏
     
    /*
    改正:
    unique_ptr p2(p1.release()); //将p1的原来内存交给另一个unique_ptr管理
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    reset函数

    • 用来重置当前unqie_ptr指针。

    • 重置之后接手另一块内存或者一直处于空状态

    unique_ptr<string> p1(new string("Hello"));
    p1.reset();//将p1置空,不指向内存对象
    unique_ptr<string> p1(new string("Hello"));
    p1.reset(nullptr);//同上
    unique_ptr<string> p1(new string("Hello"));
    unique_ptr<string> p2(new string("World"));
     
    p1.reset(p2.release());//p2置空之后,然后p1也置空,然后p1来接手p2所指向的内存
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    1.4 向unique_ptr传递删除器

      与shared_ptr类相同,unique_ptr默认情况下会调用默认析构函数来释放(delete)自己所指向的对象。不过我们也可以通过重载来指定unqie_ptr的删除器。

      与shared_ptr重载删除器不同,unique_ptr重载删除器会影响到unique_ptr类型以及如何构造(或reset)该类型的对象。

      与重载关联容器的比较操作类似,我们必须在<>中unique_ptr指向类型之后提供删除器类型。

      在创建或reset一个这种unique_ptr类型对象时,必须提供一个指定类型的可调用对象(删除器):

    //p指向一个类型为objT的对象,并使用一个类型为delT的对象释放objT对象
    //它会调用一个名为fcn的delT类型对象
    unique_ptr<objT,delT> p(new objT,fcn);
    
    • 1
    • 2
    • 3

    示例:

      我们使用decltype来指明函数类型,在后面加一个*代表函数指针

    void f(destination &d) {
        connection c=connec(&d);
        unique_ptr<connection,decltype(end_connection)*> p(&c,end_connection);
     
        ...//使用这个连接
         //当f函数退出或者异常退出,p都会调用end_connection函数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.5 unique_ptr与动态数组的使用

      标准库提供了一个可以管理new分配的数组的unique_ptr版本

    unique_ptr<int[]> arr(new int[3]{ 1,2,3 }); //定义一个指向int型数组的智能指针对象
    unique_ptr<int[]> arr2(new int[3]);
    arr.release();  //自动调用delete[]销毁其指针
    unique_ptr<int[]> arr= new int[3]{ 1,2,3 };  //错误
    unique_ptr类操作数组的方法:
    
    • 1
    • 2
    • 3
    • 4
    • 5

      动态数组的访问:unique_ptr操作数组,不提供点和箭头成员运算符,因为数组不是一个对象。但是可以通过下标运算符来访问操作数组

    unique_ptr<int[]> arr(new int[3]{ 1,2,3 });
    for (int i = 0; i < 3; ++i)
            arr[i] = i;
    
    • 1
    • 2
    • 3

    总结

    参考:

    • C++ Primer 第五版 P417-P420

    期待大家和我交流,留言或者私信,一起学习,一起进步!

  • 相关阅读:
    node环境的搭建
    基于实现地图弹窗轮播功能及遇到的问题解决
    浏览器自动化利器Selenium IDE使用指南
    自我总结项目中遇到的困难(vue篇)
    Lucene-MergePolicy详解
    【无标题】
    vue弹窗如何嵌入其它vue页面
    依赖项安全检测新利器:Scorecard API
    前端反卷计划-组件库-01-环境搭建
    工业树莓派结合USB摄像头实现远程网络监控
  • 原文地址:https://blog.csdn.net/CltCj/article/details/128058643