[C++] C++11新特性之智能指针shared_ptr和unique_ptr的使用详解_c++ std::unique_ptr和std::shared_ptr-程序员宅基地

技术标签: cpp  c++  

参考

直接管理动态内存

在C++中,动态内存的管理是通过一对运算符来完成的:

  • new:在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化

    int *pi = new int;//pi指向一个动态分配的、未初始化的无名对象
    string *ps = new string;//初始化为空string
    int *pi = new int(1024);//pi指向的对象的值为1024
    string *ps = new string(10, '9');//ps指向"9999999999"
    
  • delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存

    传递给delete的指针必须指向动态分配的内存,或者是一个空指针,释放一块并非new分配的内存,或者将相同的指针值释放多次,其行为是未定义的

    int i, *pi1 = &i, *pi2 = nullptr;
    double *pd = new double(33), *pd2 = pd;
    delete i;//错误,i不是一个指针
    delete pi1;//未定义,pi1指向一个局部变量,编译器不能分辨
    delete pd;//正确
    delete pd2;//未定义,pd2指向的内存已经被释放了,编译器不能分辨
    delete pi2;//正确,释放空指针是正确的
    
  • 动态内存的使用很容易出问题,因为确保在正确的时间释放内存是及其困难的。

    • 忘记释放内存会产生内存泄露
    • 在尚有指针引用内存的情况下就释放该内存,会产生引用非法内存的指针

为了更容易(也更安全)的使用动态内存,C++11提供了两种==智能指针==类型来管理动态对象,两种类型都定义在memory头文件中。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象

  • shared_ptr:允许多个指针指向同一个对象
  • unique_ptr:独占所指向的对象

智能指针之一:shared_ptr类

多个指针可以同时指向一个对象,当最后一个shared_ptr离开作用域时,内存才会自动释放

  • 初始化

    类似vector,智能指针也是模板。当我们创建一个智能指针时,必须提供指针可以指向的类型(在尖括号内给出类型)。

    • 默认初始化的智能指针中保存着一个空指针

      shared_ptr<string> p1;//shared_ptr,保存着一个空指针,可以指向string
      shared_ptr<list<int>> p2;//shared_ptr,保存着一个控指针,可以指向int的list
      
    • 调用make_shared库函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr,make_shared效率更高,推荐使用make_shared

      //p3指向一个值为42的int的shared_ptr
      shared_ptr<int> p3 = make_shared<int>(42);
      //p4指向一个值初始化的int,值为0
      shared_ptr<int> p4 = make_shared<int>();
      //使用auto定义一个对象来保存make_shared_ptr的结果
      auto p5 = make_shared<vector<int>>();
      
  • 引用计数

    当进行拷贝或者赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。每个shared_ptr都有一个关联的计数器,称为引用计数

    • 无论何时拷贝一个shared_ptr,计数器都会递增
    • 给shared_ptr赋予一个新值或者shared_ptr被销毁,计数器就会递减
    • 一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象
    auto p = make_shared<int>(25);//p指向的对象只有p一个引用者
    auto q(p);//p和q指向同一个对象25
    
    auto r = make_shared<int>(32);//r指向的int只有一个引用者
    r = q;//给r赋予一个新值,q指向的对象的计数器递增,r指向的对象的计数器递减,r原来指向的32对象已经没有引用者,自动释放
    

    shared_ptr的析构函数来递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

  • shared_ptr和new结合使用

    如果不初始化一个智能指针,它就会被默认初始化为一个空指针。默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放它所关联的对象。

    • 用new返回的指针来初始化智能指针

      接受指针参数的智能指针构造函数是explicit。因此,不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针。

      shared_ptr<int> p1 = new int(1024);//错误,必须使用直接初始化形式
      shared_ptr<int> p(new int(1024));//正确,使用了直接初始化形式,p指向一个值为1024的int
      
    • 返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针

      shared_ptr<int> tarversal(int p) {
          return new int(p);//错误:隐式转换为shared_ptr<int>
      }
      
      shared_ptr<int> traversal(int p) {
          return shared_ptr<int>(new int(p));//正确:显示地用int*创建shared_ptr<int>
      }
      
  • 其他shared_ptr操作

    • 用**reset()**来将一个新的指针赋予一个shared_ptr

      p = new int(1024);//错误,不能将一个指针赋予shared_ptr
      p.reset(new int(1024));//正确,p指向一个新对象
      

      与赋值类似,reset会更新引用计数,如果需要的话,会释放p指向的对象。

      p.reset();//若p是唯一指向其对象的shared_ptr,reset会释放此对象
      p.reset(q);//若传递了可选的参数内置指针q,会令p指向q,否则会将p置为空
      p.reset(q, d);//若还传递了参数d,将会调用d,而不是delete来释放q
      
    • unique判断是否是唯一指向当前内存的shared_ptr

      返回 shared_ptr 对象是否不与其他 shared_ptr 对象共享其指针的所有权(即,它是唯一的)。空指针从来都不是唯一的(因为它们不拥有任何指针)。此函数应返回与 (use_count()==1) 相同的值,尽管它可能以更有效的方式执行此操作。

      true:当前shared_ptr是唯一的

      std::shared_ptr<int> foo;//shared_ptr,保存着一个空指针,可以指向int
      std::shared_ptr<int> bar (new int);//shared_ptr,指向一个int对象
      std::cout << "1: " << foo.unique() << '\n';  // false,空指针从来都不是唯一的
      foo = bar;
      std::cout << "2: " << foo.unique() << '\n';  // false,foo和bar共同指向同一个int对象
      bar = nullptr;
      std::cout << "3: " << foo.unique() << '\n';  // true
      
      
    • 用**get()**函数获取智能指针中保存的原始指针

      要小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了

      get用来将指针的访问权限传递给代码,你只有在确定代码不会delete指针的情况下,才能使用get。特别是,永远不要用get初始化另一个智能指针或者另一个智能指针赋值

      int* p = new int (10);
      std::shared_ptr<int> a (p);
      if (a.get()==p)std::cout << "a and p point to the same location\n";
      //以下三种形式访问同一块地址
      std::cout << *a.get() << "\n";//输出10
      std::cout << *a << "\n";//输出10
      std::cout << *p << "\n";//输出10
      
  • shared_ptr陷阱

    • void main( )
      {
       shared_ptr<int> sptr1( new int );
       shared_ptr<int> sptr2 = sptr1;
       shared_ptr<int> sptr3;
       sptr3 =sptr1
      }
      

      上述代码中引用计数变化情况:

      所有的shared_ptr拥有相同的引用计数,属于相同的组。

    • void main( )
      {
       int* p = new int;
       shared_ptr<int> sptr1(p);
       shared_ptr<int> sptr2(p);
      }
      

      上述代码出现了一个错误,因为两个来自不同组的shared_ptr指向同一个资源

      为了避免这个问题,尽量不要从一个裸指针创建shared_ptr,此外万一在智能指针作用域结束之前删除了普通指针p,又将是一个错误。

    • class B;
      class A
      {
      public:
       A(  ) : m_sptrB(nullptr) { };
       ~A( )
       {
        cout<<" A is destroyed"<<endl;
       }
       shared_ptr<B> m_sptrB;
      };
      class B
      {
      public:
       B(  ) : m_sptrA(nullptr) { };
       ~B( )
       {
        cout<<" B is destroyed"<<endl;
       }
       shared_ptr<A> m_sptrA;
      };
      //***********************************************************
      void main( )
      {
       shared_ptr<B> sptrB( new B );
       shared_ptr<A> sptrA( new A );
       sptrB->m_sptrA = sptrA;
       sptrA->m_sptrB = sptrB;
      }
      

      上述代码产生了一个循环引用A对B有一个shared_ptr,B对A也有一个shared_ptr,与sptrA和sptrB关联的资源都没有被释放

      当sptrA和sptrB离开作用域时,它们的引用计数都只减少到1,所以它们指向的资源并没有释放

智能指针之二:unique_ptr类

unique_ptr也是对auto_ptr的替换。unique_ptr遵循着独占语义。在任何时间点,资源只能唯一地被一个unique_ptr占有。当unique_ptr离开作用域,所包含的资源被释放。如果资源被其它资源重写了,之前拥有的资源将被释放。所以它保证了他所关联的资源总是能被释放

  • 初始化

    与shared_ptr不同,没有类似make_shared的标准库返回一个unique_ptr。定义unique_ptr的时候需要将其绑定到一个new返回的指针上,类似shared_ptr,初始化unique_ptr的时候必须采用直接初始化的形式。

    unique_ptr<double> p;//可以指向一个int的unique_ptr
    unique_ptr<int> p1(new int(42));//unique_ptr,指向一个值为42的int
    
    //unique_ptr不支持拷贝或赋值操作
    unique_ptr<string> p2(new string("aabbcc"));//unique_ptr,指向一个值为aabbcc的string
    unique_ptr<string> p3(p2);//错误,unique_ptr不支持拷贝操作
    unique_ptr<string> p4;
    p4 = p2;//错误,unique_ptr不支持赋值操作
    
  • 转移所有权

    调用release或者reset将指针的所有权转移

    release成员返回unique_ptr当前保存的指针并将其置为空,释放所有权不释放资源

    reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针,释放所有权也释放资源

    unique_ptr<string> p1(new string("aabbcc"));//unique_ptr,指向一个值为aabbcc的string
    unique_ptr<string> p2(p1.release());//将所有权从p1转移给p2,release将p1置为空
    
    unique_ptr<string> p3(new string("tex"));//unique_ptr,指向一个值为tex的string
    p2.reset(p3.release);//将所有权从p3转移到p2,reset释放了p2原来指向的内存
    
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wangmj_hdu/article/details/119413849

智能推荐

稀疏编码的数学基础与理论分析-程序员宅基地

文章浏览阅读290次,点赞8次,收藏10次。1.背景介绍稀疏编码是一种用于处理稀疏数据的编码技术,其主要应用于信息传输、存储和处理等领域。稀疏数据是指数据中大部分元素为零或近似于零的数据,例如文本、图像、音频、视频等。稀疏编码的核心思想是将稀疏数据表示为非零元素和它们对应的位置信息,从而减少存储空间和计算复杂度。稀疏编码的研究起源于1990年代,随着大数据时代的到来,稀疏编码技术的应用范围和影响力不断扩大。目前,稀疏编码已经成为计算...

EasyGBS国标流媒体服务器GB28181国标方案安装使用文档-程序员宅基地

文章浏览阅读217次。EasyGBS - GB28181 国标方案安装使用文档下载安装包下载,正式使用需商业授权, 功能一致在线演示在线API架构图EasySIPCMSSIP 中心信令服务, 单节点, 自带一个 Redis Server, 随 EasySIPCMS 自启动, 不需要手动运行EasySIPSMSSIP 流媒体服务, 根..._easygbs-windows-2.6.0-23042316使用文档

【Web】记录巅峰极客2023 BabyURL题目复现——Jackson原生链_原生jackson 反序列化链子-程序员宅基地

文章浏览阅读1.2k次,点赞27次,收藏7次。2023巅峰极客 BabyURL之前AliyunCTF Bypassit I这题考查了这样一条链子:其实就是Jackson的原生反序列化利用今天复现的这题也是大同小异,一起来整一下。_原生jackson 反序列化链子

一文搞懂SpringCloud,详解干货,做好笔记_spring cloud-程序员宅基地

文章浏览阅读734次,点赞9次,收藏7次。微服务架构简单的说就是将单体应用进一步拆分,拆分成更小的服务,每个服务都是一个可以独立运行的项目。这么多小服务,如何管理他们?(服务治理 注册中心[服务注册 发现 剔除])这么多小服务,他们之间如何通讯?这么多小服务,客户端怎么访问他们?(网关)这么多小服务,一旦出现问题了,应该如何自处理?(容错)这么多小服务,一旦出现问题了,应该如何排错?(链路追踪)对于上面的问题,是任何一个微服务设计者都不能绕过去的,因此大部分的微服务产品都针对每一个问题提供了相应的组件来解决它们。_spring cloud

Js实现图片点击切换与轮播-程序员宅基地

文章浏览阅读5.9k次,点赞6次,收藏20次。Js实现图片点击切换与轮播图片点击切换<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title></title> <script type="text/ja..._点击图片进行轮播图切换

tensorflow-gpu版本安装教程(过程详细)_tensorflow gpu版本安装-程序员宅基地

文章浏览阅读10w+次,点赞245次,收藏1.5k次。在开始安装前,如果你的电脑装过tensorflow,请先把他们卸载干净,包括依赖的包(tensorflow-estimator、tensorboard、tensorflow、keras-applications、keras-preprocessing),不然后续安装了tensorflow-gpu可能会出现找不到cuda的问题。cuda、cudnn。..._tensorflow gpu版本安装

随便推点

物联网时代 权限滥用漏洞的攻击及防御-程序员宅基地

文章浏览阅读243次。0x00 简介权限滥用漏洞一般归类于逻辑问题,是指服务端功能开放过多或权限限制不严格,导致攻击者可以通过直接或间接调用的方式达到攻击效果。随着物联网时代的到来,这种漏洞已经屡见不鲜,各种漏洞组合利用也是千奇百怪、五花八门,这里总结漏洞是为了更好地应对和预防,如有不妥之处还请业内人士多多指教。0x01 背景2014年4月,在比特币飞涨的时代某网站曾经..._使用物联网漏洞的使用者

Visual Odometry and Depth Calculation--Epipolar Geometry--Direct Method--PnP_normalized plane coordinates-程序员宅基地

文章浏览阅读786次。A. Epipolar geometry and triangulationThe epipolar geometry mainly adopts the feature point method, such as SIFT, SURF and ORB, etc. to obtain the feature points corresponding to two frames of images. As shown in Figure 1, let the first image be ​ and th_normalized plane coordinates

开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先抽取关系)_语义角色增强的关系抽取-程序员宅基地

文章浏览阅读708次,点赞2次,收藏3次。开放信息抽取(OIE)系统(三)-- 第二代开放信息抽取系统(人工规则, rule-based, 先关系再实体)一.第二代开放信息抽取系统背景​ 第一代开放信息抽取系统(Open Information Extraction, OIE, learning-based, 自学习, 先抽取实体)通常抽取大量冗余信息,为了消除这些冗余信息,诞生了第二代开放信息抽取系统。二.第二代开放信息抽取系统历史第二代开放信息抽取系统着眼于解决第一代系统的三大问题: 大量非信息性提取(即省略关键信息的提取)、_语义角色增强的关系抽取

10个顶尖响应式HTML5网页_html欢迎页面-程序员宅基地

文章浏览阅读1.1w次,点赞6次,收藏51次。快速完成网页设计,10个顶尖响应式HTML5网页模板助你一臂之力为了寻找一个优质的网页模板,网页设计师和开发者往往可能会花上大半天的时间。不过幸运的是,现在的网页设计师和开发人员已经开始共享HTML5,Bootstrap和CSS3中的免费网页模板资源。鉴于网站模板的灵活性和强大的功能,现在广大设计师和开发者对html5网站的实际需求日益增长。为了造福大众,Mockplus的小伙伴整理了2018年最..._html欢迎页面

计算机二级 考试科目,2018全国计算机等级考试调整,一、二级都增加了考试科目...-程序员宅基地

文章浏览阅读282次。原标题:2018全国计算机等级考试调整,一、二级都增加了考试科目全国计算机等级考试将于9月15-17日举行。在备考的最后冲刺阶段,小编为大家整理了今年新公布的全国计算机等级考试调整方案,希望对备考的小伙伴有所帮助,快随小编往下看吧!从2018年3月开始,全国计算机等级考试实施2018版考试大纲,并按新体系开考各个考试级别。具体调整内容如下:一、考试级别及科目1.一级新增“网络安全素质教育”科目(代..._计算机二级增报科目什么意思

conan简单使用_apt install conan-程序员宅基地

文章浏览阅读240次。conan简单使用。_apt install conan