码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【C++】RAll,裸指针,auto_ptr


    文章目录

    • 什么是RAll?
      • 1. RAll的原理:
        • 1.1 资源的获取一般分为三个阶段:
        • 1.2 资源的销毁往往是程序员经常忘掉的环节,所以程序界就想,如何才能让资源自动销毁?
    • 裸指针
      • 什么是裸指针?
      • 裸指针带来的问题:
        • 问题总结:
    • 智能指针:
      • 1. 为什么要抛弃auto_ptr
    • 关于: `my_auto_ptr obj(new Object(10));`的实现过程。
        • 最终内存图:
      • 2. 重载->和*
      • 3. 重置指向(reset )
      • 4. 释放函数(release)
        • 4.1 出现的问题(1):多次析构同一个堆空间导致程序崩溃。
        • 4.2 出现的问题(2):失去拥有权导致程序崩溃
        • 4.3 问题(3):不清楚生成的是一个对象还是多个对象,析构失败。
      • 总结:
    • 什么是RAll?

      使用局部对象****来管理资源的技术称之为资源获取即初始化。
      这里的资源主要是指操作系统中有限的东西,如:内存,网络套接字,互斥量,文件句柄等等,局部对象实质存储在栈的对象。它的生命周期是由操作系统管理的。无需人工的介入。

      1. RAll的原理:

      1.1 资源的获取一般分为三个阶段:

      1.创建资源 (创建对象)
      2.使用资源
      3.获取资源 (调用析构函数)

      1.2 资源的销毁往往是程序员经常忘掉的环节,所以程序界就想,如何才能让资源自动销毁?

      答: 解决的方案就是RAll,它充分利用了C++语言局部对象,自动销毁的特性来控制资源的生命周期。
      局部对象就是在函数中的一个对象,调动函数,对象被产生,函数结束,对象被销毁。局部的函数的创建和销毁分别调用了构造函数和析构函数。

      裸指针

      在研究智能指针之前,我们先探讨一下裸指针的概念。

      什么是裸指针?

      如下:

      int main()
      {
         int *ip;//未初始化,野指针
         int *ip =NULL;  //裸指针
         Object *op = NULL;  //裸指针
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      通俗的讲,凡是带*并且初始化的都是裸指针。

      裸指针带来的问题:

      1.无法判断裸指针指向的是一个对象还是一组对象。
      2.

      class Object
      {
      private:
          int value;
      public:
          Object(int x = 0) :value(x) { cout << "create Object" << this << endl;}
          ~Object() { cout << "destory Obejct" << this << endl; }
          int& value(){}
        
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      添加一个fun()

      void fun(Object* op) //传给op的&obj和arobj ,我们无法判断传给op的是一个对象还是一组对象。
      {
      }
      int main()
      {
      Object obj(10);
      Object arobj[10];
          fun(&obj);   //无法判断,第一次传给op的是一个对象
          fun(aobj);  //第二次传的是一组对象。
       }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      无法判断,第一次传给op的是一个对象,第二次传的是一组对象。
      再添加一个Destory()

      void Destory(Object* op) 
      {
              delete op;
              delete[]op;
          }
      int main()
      {
         Object *op = new Obejct(10);
         Object *arop = new Object[10];
         Destory(op);  //析构的时候也根本没办法不知道,是析构一组对象,还是一个对象。
         Destory(arop);
         
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      析构的时候也根本没办法不知道,是析构一组对象,还是一个对象。所以会将delete op和delete[]op通通执行。
      往类中添加一个方法。

      int& value(){}
      };
      int main()
      {
         Object *op =NULL;
         op= new Object(10);
         op->value();
         Destory(op);
         //op此时是失效指针,空悬指针
      //  也无法判断裸指针是否被释放。
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      因此可以总结关于裸指针的以下几点问题:

      问题总结:

      1.难以区分指向的是单个对象还是一组对象。
      2.使用完指针之后,无法判断是否应该销毁指针,因此也就无法判断指针是否“拥有”指向的对象。
      3.在已经确定需要销毁指针的情况下,也无法确定是用delete关键字删除,还是其他特殊的销毁机制,例如通过将指针传入某个特定的销毁函数来销毁指针。
      4.即便已经确定了销毁的方法,,由于1的原因,无法确认用delete还是delete[]。
      5.假设上述问题都解决了,也很难保证在代码的所有路径中(分支结构,异常导致的跳转),有且仅有一次销毁指针的操作,任何一条路径一楼都会导致内存泄漏。
      6.理论上没有办法来分辨一个指针是否处于悬挂状态。

      所以要使用智能指针。

      智能指针:

      c11:
         unique_ptr;
         share_ptr;
         weak_ptr;
      被抛弃:
          auto_ptr
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6

      1. 为什么要抛弃auto_ptr

      首先我们先将auto_ptr实现一下,我们就可以从中的找到不使用它 的原因。
      Object类可以作为类模板

      class Object 
      {
          int value;
      public:
          Object(int x = 0) :value(x) { cout << "create Object" << endl; }
          ~Object() { cout << "Destory Object" << endl; }
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      template<class _Ty>
      class my_auto_ptr
      {
      private:
          bool _Owns;   //拥有权
          _Ty* _Ptr;  //指针
      public:
          //构造函数
          my_auto_ptr(_Ty* p = NULL) :Owns(p != NULL), _Ptr(p) {}
          //析构函数
          ~my_auto_ptr()
          {
              if (_Owns)
              {
                  delete _Ptr;
              }
                  _Owns = false;
                  _Ptr = NULL;
          }
      };
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20

      测试:

      int main()
      {
                    //_Ty = Object
        my_auto_ptr<Object> obj(new Object(10));
                              //1.new完成返回的是一个对象的地址
      }                      //2.调用my_auto_ptr类的构造函数创建obj对象,
                             //3.p指向Object不具名对象的地址。
                             //4. p->value =10  _Ptr(p)  _Ptr->value = 10;
        return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

      obj 是智能指针对象。
      obj对象中的 _ptr指针的类型是Object。
      因此_ptr指针指向的Object类内存空间。


      关于: my_auto_ptr obj(new Object(10));的实现过程。

      第一步:new Object(10)
      new Object(10)调用构造函数,创建对象,申请堆空间,对value进行初始化
      请添加图片描述
      1.new完成返回的是一个对象的地址,
      请添加图片描述

      1. 调用my_auto_ptr类的构造函数创建obj对象,
      2. p指向Object不具名对象的地址。
      3. p->value =10 _Ptr§ _Ptr->value = 10;
        请添加图片描述

      最终内存图:

      请添加图片描述


      接下来当我们在Object类中定义两个方法:

      int& value()
      {
          return value;
      }
      const int& value()const { return value; }
      
      • 1
      • 2
      • 3
      • 4
      • 5

      在类外使用fun()函数来访问value()

      void fun()
      {
        my_auto_Ptr<Object> obj(new Object(10));
        //obj->_Ptr->value()
        cout << obj->value() << endl;
        //*(obj._Ptr).value()
          cout << (*obj).value() << endl;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      我们想要直接通过对象使用"->“和” * "访问成员函数就需要对这两个运算符进行重载:

      2. 重载->和*

      如下:
      在my_auto_ptr中写入

      _Ty *get()const{return _Ptr}
      _Ty &operator *()const
      {
         return *(get()); 
      }
      _Ty *operator->()const
      {
       return get();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9

      说白了就是转换成直接使用Object类的指针访问Object类中的value()函数。
      对于内置类型:如:(遵循的是C的概念)

      int *ip = new int(10);
      *ip = 100;
      
      • 1
      • 2
      • 解释为解引用。
        而class 是遵循C++的概念。
        C++完全兼容C。
        智能指针不处理带const的指针。

      3. 重置指向(reset )

      就是my_auto_ptr对象obj中的_ptr指针指向另外一个对象。

      void reset(_Ty *p=NULL)
      {
        if(_Owns)
        {
          delete _Ptr;
        }
        _Ptr = p;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8

      4. 释放函数(release)

      释放函数就是释放对当前对象的拥有权。

      4.1 出现的问题(1):多次析构同一个堆空间导致程序崩溃。

      _Ty  *release()const
      {
          _Ty*tmp = NULL;
          if(_Owns)
          {
              //强转成普通类型的指针
              ((my_auto_ptr*)this)->_Owns  = false;
              tmp = _Ptr;
              ((my_auto_ptr*)this)->_Ptr = NULL;
          }
          return tmp;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12

      将一个对象赋值给另外一个对象

      //加入
      //this指针只想的是obja  ,p是obj的引用
      my_auto_ptr(const _Ty &p):_Owns(p._Owns)
      {
        if(_Owns)
        {
           _Ptr = p._Ptr;
        }
      }
      int main()
      {
         my_auto_ptr<Object> obj(new Object(10));
         my_auto_ptr<Object>obja(obj);
         //两个对象保存同一个堆空间,析构时导致内存泄漏。
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

      两个对象保存同一个堆空间,析构时导致内存泄漏。
      使用release将obj的资源转给obja
      重写构造函数。

      //拷贝构造函数必须以引用的方式传值
      my_auto_ptr(const _Ty &p):_Owns(p._Owns),_Ptr(p,release())
      {
        if(_Owns)
        {
           _Ptr = p._Ptr;
        }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      void fun(my_auto_ptr<Object> obja)
      {
         int x =  obja->value();  //可以正常执行。
      }
      int main()
      {
         my_auto_ptr<Object>obj(new Object(10));
         fun(obj);  
         //此时由于调用拷贝构造,并使obj失去了对对象的拥有权,
         int a = obj->value(); //obj自然也无法调动运算符,程序崩溃。
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11

      传参:调用拷贝构造函数,此时会创建一个临时对象,给其_Owns传入obj的_Owns,使用使用release(),让obj失去对对象的拥有(_Owns = false),让tmp指向obj中_Ptr,return tmp;临时对象中的_Ptr指向tmp,将临时对象的地址传给obja。传参结束。析构临时对象,fun()函数结束,执行析构函数,析构obja。

      4.2 出现的问题(2):失去拥有权导致程序崩溃

      此时obj已经失去了对对象的拥有权,因此也就无法执行obj->value()。
      图示:
      请添加图片描述
      赋值运算符重载还是和上面拷贝构造函数出现的问题一样,要么导致内存泄漏,要么obj的将失去对象的拥有权。在接下来执行其他程序就会出现问题。

      4.3 问题(3):不清楚生成的是一个对象还是多个对象,析构失败。

      我们申请的时候申请一组对象,但是调用析构函数析构,却析构的第一个对象,程序就会崩溃,_ptr无法分辨,传入的是一组对象还是一个对象。
      不知道使用delete还是delete[]。

      int main(void)
      {
      	my_auto_ptr<Object> obja(new Object(10)); // 正确
      
      	my_auto_ptr<Object> objb(new Object[10]); // 错误
      	return 0;
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7

      总结:

      int main()
      {
           my_auto_ptr<Object> obj(new Object(10));
            my_auto_ptr<Object>obja(obj);
            使用release可以把obj的资源给obja。
            但是执行下面的程序
            fun(obj);  
             int a = obj->value(); //会失去对obj的拥有权。这个语句又无法执行。
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      my_auto_ptr(const _Ty &p):_Owns(p._Owns),_Ptr(p,release())
      
      • 1

      拷贝构造函数,怎么写都不对,意义不明确,因此就将auto_ptr 弃用了。
      由于在c11之前没有移动构造的概念,所以这些问题一直也就解决不了。

      因此,由于这一系类问题:对auto_ptr弃用,从从c17中移除。

    • 相关阅读:
      Spring Cache+Redis缓存数据
      12个有效降低论文重复率的实用策略分享
      SpringBoot全局异常处理源码
      Webpack配置entry修改入口文件或打包多个文件
      为什么我的视频播放量上不去?
      2022吴恩达机器学习课程——第一课
      初探富文本之React实时预览
      解密Prompt系列31. LLM Agent之从经验中不断学习的智能体
      二极管如何工作?
      Leetcode—2331.计算布尔二叉树的值【简单】
    • 原文地址:https://blog.csdn.net/weixin_52958292/article/details/127599579
      • 最新文章
      • 攻防演习之三天拿下官网站群
        数据安全治理学习——前期安全规划和安全管理体系建设
        企业安全 | 企业内一次钓鱼演练准备过程
        内网渗透测试 | Kerberos协议及其部分攻击手法
        0day的产生 | 不懂代码的"代码审计"
        安装scrcpy-client模块av模块异常,环境问题解决方案
        leetcode hot100【LeetCode 279. 完全平方数】java实现
        OpenWrt下安装Mosquitto
        AnatoMask论文汇总
        【AI日记】24.11.01 LangChain、openai api和github copilot
      • 热门文章
      • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
        奉劝各位学弟学妹们,该打造你的技术影响力了!
        五年了,我在 CSDN 的两个一百万。
        Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
        面试官都震惊,你这网络基础可以啊!
        你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
        心情不好的时候,用 Python 画棵樱花树送给自己吧
        通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
        13 万字 C 语言从入门到精通保姆级教程2021 年版
        10行代码集2000张美女图,Python爬虫120例,再上征途
      Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1
      正则表达式工具 cron表达式工具 密码生成工具

      京公网安备 11010502049817号