智能指针 订阅
当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。 展开全文
当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。智能指针(smart pointer)的一种通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。
信息
工作机制
类似C++的内置指针
外文名
smart pointer
经典策略
引入辅助类、使用句柄类
含    义
存储指向动态分配对象指针的类
中文名
智能指针
智能指针原理
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。实现引用计数有两种经典策略:一是引入辅助类,二是使用句柄类。下面分别介绍这些内容
收起全文
精华内容
下载资源
问答
  • 不过系统为我们提供了智能指针,避免出现上述问题,本文将系统地分析Android系统智能指针(轻量级指针、强指针和弱指针)的实现原理。在使用C++来编写代码的过程中,指针使用不当造成内存泄漏一般就是因为new了一个...
  • 一直以来都对智能指针一知半解,看C++Primer中也讲的不够清晰明白(大概是我功力不够吧)。近花了点时间认真看了智能指针,特地来写这篇文章。  智能指针是什么  简单来说,智能指针是一个类,它对普通指针进行...
  • 该demo主要展现智能指针和引用计数的实现过程,总共两个类,一个智能指针类,一个引用计数类,另外附一份执行步骤资料
  • 此文描述了智能指针unique_ptr的用法的相关介绍,具体实例请看下文  unique_ptr是独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr,如下面错误...
  • C++智能指针用法详解

    2020-12-22 16:44:49
     用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法。包括:std::auto_ptr、boost::scoped_ptr、boost::shared_ptr、boost::scoped_array、boost::shared_array、boost::weak_ptr、boost:: ...
  • C++智能指针实例详解

    2020-12-31 20:04:38
    本文通过实例详细阐述了C++关于智能指针的概念及用法,有助于读者加深对智能指针的理解。详情如下: 一、简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,...
  • c++11智能指针

    2021-01-06 17:08:02
    智能指针  智能指针是行为类似于指针的类对象。可以帮助管理动态内存分配。普通指针分配内存后都必须释放内存 new 之后需要使用 delete 否则内存泄漏。智能指针类对象封装了自动释放内存的功能 从而避免内存泄漏 ...
  • xx智能指针:防止用户忘记释放掉指针所指的堆空间而造成内存泄漏 当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它 智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象...
  • 前言:C++中智能指针的引入,使得开发人员在与内存的斗争中占据上峰。然而凡事都不会尽善尽美,智能指针的循环引用缺陷还是会引发令人谈虎色变的内存泄露。本文的内容是讲述,如何解决循环引用带来的内存问题。  ...
  • 1.简介 ...智能指针是C++程序员们一件管理内存的利器,使用智能指针管理内存资源,实际上就是将申请的内存资源交由智能指针来管理,是RAII技术的一种实现。RAII是C++的之父Bjarne Stroustrup教授提
  • 但像java等其他一些语言则不会有这样的问题,为什么呢,因为它们有很好的处理内存的方法,比如java的垃圾回收机制,现在,我们c++终于也有了智能指针。 1. 什么是智能指针 简单地说,智能指针是用对象去管理一个...
  • c++智能指针介绍 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete,比如流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见,并造成内存泄露。...
  • C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于...
  • 这是C++11新特性介绍的第五部分,涉及到智能指针的相关内容(shared_ptr, unique_ptr, weak_ptr)。  不想看toy code的读者可以直接拉到文章后看这部分的总结。  shared_ptr  shared_ptr 基本用法  shared_...
  • 智能指针错误用法demo

    2020-11-29 13:40:14
    智能指针错误用法示例集合,目前整理了 6 种,虽然示例程序中很直观,但是实际使用的时候会以类似的情形出现,一定要提高警惕,不知道这些例子中有没有自己的影子呢?
  • 主要介绍了 C++11智能指针之weak_ptr详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
  • 为什么需要智能指针?因为c++的内存管理一直是个令人头疼的问题。 假如我们有如下person对象:每个person有自己的名字,并且可以告诉大家他叫什么名字 http://blog.csdn.net/qq_17242957/article/details/52404271
  • 在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr 。 往期文章参考: 【C++11新特性】 C++11 智能指针之shared_ptr 【C++11新...
  • 我很喜欢新的C++11的智能指针。在很多时候,对很多讨厌自己管理内存的人来说是天赐的礼物。在我看来,C++11的智能指针能使得C++新手教学更简单。  其实,我已经使用C++11两年多了,我无意中发现多种错误使用C++11...
  • C++智能指针详解

    2021-07-23 10:44:22
    new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针; delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。 动态内存管理经常会出现两种问题: (1)一种是忘记释放内存,会造成内存泄漏...


    1、概述

    我们知道除了静态内存和栈内存外,每个程序还有一个内存池,这部分内存被称为自由空间或者堆。程序用堆来存储动态分配的对象即那些在程序运行时分配的对象,当动态对象不再使用时,我们的代码必须显式的销毁它们。

    在C++中,动态内存的管理是用一对运算符完成的:new和delete
    new:在动态内存中为对象分配一块空间并返回一个指向该对象的指针;
    delete:指向一个动态独享的指针,销毁对象,并释放与之关联的内存。

    动态内存管理经常会出现两种问题:
    (1)一种是忘记释放内存,会造成内存泄漏;
    (2)一种是尚有指针引用内存的情况下就释放了它,就会产生引用非法内存的指针。

    为了更加容易(更加安全)的使用动态内存,引入了智能指针的概念。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。

    标准库提供的两种智能指针的区别在于管理底层指针的方法不同:shared_ptr和unique_ptr
    (1)shared_ptr允许多个指针指向同一个对象;
    (2)unique_ptr则“独占”所指向的对象。

    标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象,这三种智能指针都定义在memory头文件中。


    2、auto_ptr (不要使用的指针)

    (1)内部大概实现:做成一个auto_ptr类,包含原始指针成员。
    当auto_ptr类型的对象被释放时,利用析构函数,将拥有的原始指针delete掉。

    //大概长这个样子(简化版)
    template<class T>
    class auto_ptr
    {  
        T* ptr;
    };
    

    (2)示例用法:

     void runGame()
    {
      std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物
      monster1->doSomething();//怪物做某种事
    }//runGame函数执行完时,monster1被释放,然后它的析构函数也把指向的一个怪物释放了
    

    复制auto_ptr对象时,把指针指传给复制出来的对象,原有对象的指针成员随后重置为nullptr。
    这说明auto_ptr是独占性的,不允许多个auto_ptr指向同一个资源。

    void runGame()
    {
         std::auto_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物
         monster1->doSomething();						//怪物做某种事
         std::auto_ptr<Monster> monster2 = monster1;	//转移指针
         monster2->doSomething();						//怪物做某种事
         monster1->doSomething();						//Oops!monster1智能指针指向了nullptr,运行期崩溃。
    }
    

    注意:
    虽然本文简单介绍了auto_ptr。
    但是不要用auto_ptr! 不要用auto_ptr!

    虽然它是c++11以前的最原始的智能指针,但是在c++11中已经被弃用(使用的话会被警告)了。
    它的替代品,也就是c++11新智能指针unique_ptr,shared_ptr,weak_ptr。


    3、shared_ptr(一种强引用指针)

    多个shared_ptr指向同一处资源,当所有shared_ptr都全部释放时,该处资源才释放。
    (有某个对象的所有权(访问权,生命控制权) 即是 强引用,所以shared_ptr是一种强引用型指针)

    (1)内部大概实现:每次复制,多一个共享同处资源的shared_ptr时,计数+1。每次释放shared_ptr时,计数-1。
    当shared计数为0时,则证明所有指向同一处资源的shared_ptr们全都释放了,则随即释放该资源(哦,还会释放new出来的SharedPtrControlBlock)。

    //shared计数放在这个结构体里面,实际上结构体里还应该有另一个weak计数。下文介绍weak_ptr时会解释。
    struct SharedPtrControlBlock
    {  
      int shared_count;
    };
    //大概长这个样子(简化版)
    template<class T>
    class shared_ptr
    {  
      T* ptr;  
      SharedPtrControlBlock* count;
    };
    

    (2)示例用法:

    void runGame()
    {  
        std::shared_ptr<Monster> monster1(new Monster());  //计数加到1
     do{
           std::shared_ptr<Monster> monster2 = monster1; 	//计数加到2  
        }while(0);          
      //该栈退出后,计数减为1,monster1指向的堆对象仍存在
      std::shared_ptr<Monster> monster3 = monster1;   	//计数加到2
    }//该栈退出后,shared_ptr都释放了,计数减为0,它们指向的堆对象也能跟着释放.
    

    缺陷:模型循环依赖(互相引用或环引用)时,计数会不正常

    假如有这么一个怪物模型,它有2个亲人关系:

    class Monster
    {  
        std::shared_ptr<Monster> m_father;  
        std::shared_ptr<Monster> m_son;
    
    public:  
        void setFather(std::shared_ptr<Monster>& father);      
        void setSon(std::shared_ptr<Monster>& son);    
        ~Monster(){std::cout << "A monster die!";}       
    };
    
    void runGame()
    {
         std::shared_ptr<Monster> father = new Monster();
         std::shared_ptr<Monster> son = new Monster();
         father->setSon(son);
         son->setFather(father);
    }
    

    函数退出时栈的shared_ptr对象陆续释放后的情形:
    (1)一开始:father,son指向的堆对象 shared计数都是为2;
    (2)son智能指针退出栈:son指向的堆对象 计数减为1,father指向的堆对象 计数仍为2。
    (3)father智能指针退出栈:father指向的堆对象 计数减为1 , son指向的堆对象 计数仍为1。
    (4)函数结束:所有计数都没有变0,也就是说中途没有释放任何堆对象。

    为了解决这一缺陷的存在,弱引用指针weak_ptr的出现很有必要。

    --------->
    创建智能指针时必须提供额外的信息,指针可以指向的类型:

    shared_ptr<string>p1;
    shared_ptr<list<int>> p2;
    

    默认初始化的智能指针中保存着一个空指针。
    智能指针的使用方式和普通指针类似,解引用一个智能指针返回它指向的对象,在一个条件判断中使用智能指针就是检测它是不是空。

    if(p1 && p1->empty())
      *p1 = "hi";
    

    如下表所示是shared_ptr和unique_ptr都支持的操作:
    在这里插入图片描述
    如下表所示是shared_ptr特有的操作:
    在这里插入图片描述
    函数说明
    1)make_share函数
    最安全的分配和使用动态内存的方法就是调用一个名为make_shared的标准库函数,此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。头文件和share_ptr相同,在memory中 。

    必须指定想要创建对象的类型,定义格式见下面例子:

    shared_ptr<int> p3 = make_shared<int>(42);
    shared_ptr<string> p4 = make_shared<string>(10,'9');
    shared_ptr<int> p5 = make_shared<int>();
    

    make_shared用其参数来构造给定类型的对象,如果我们不传递任何参数,对象就会进行值初始化。


    2)shared_ptr的拷贝和赋值
    当进行拷贝和赋值时,每个shared_ptr都会记录1有多少个其他shared_ptr都会记录有多少个其他shared_ptr指向相同的对象。

    auto p = make_shared<int>(42);
    auto q(p);
    

    我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论何时我们拷贝一个shared_ptr,计数器都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减,一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

    auto r = make_shared<int>(42); 	//r指向的int只有一个引用者
    
    r=q;							//给r赋值,令它指向另一个地址
    								//递增q指向的对象的引用计数
    								//递减r原来指向的对象的引用计数
    								//r原来指向的对象已没有引用者,会自动释放
    


    3)shared_ptr自动销毁所管理的对象
    当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象,它是通过另一个特殊的成员函数-析构函数完成销毁工作的,类似于构造函数,每个类都有一个析构函数。

    析构函数控制对象销毁时做什么操作。析构函数一般用来释放对象所分配的资源。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它所占用的内存。


    4)shared_ptr还会自动释放相关联的内存
    当动态对象不再被使用时,shared_ptr类还会自动释放动态对象,这一特性使得动态内存的使用变得非常容易。如果你将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

    使用了动态生存期的资源的类,程序使用动态内存的原因:

    • 程序不知道自己需要使用多少对象
    • 程序不知道所需对象的准确类型
    • 程序需要在多个对象间共享数据


    5)不要混合使用普通指针和智能指针
    如果混合使用的话,智能指针自动释放之后,普通指针有时就会变成悬空指针,当将一个shared_ptr绑定到一个普通指针时,我们就将内存的管理责任交给了这个shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问shared_ptr所指向的内存了。

    也不要使用get初始化另一个智能指针或为智能指针赋值。

    shared_ptr<int> p(new int(42));//引用计数为1
    int *q = p.get();//正确:但使用q时要注意,不要让它管理的指针被释放
    {
        //新程序块
    
        //未定义:两个独立的share_ptr指向相同的内存
        shared_ptr(q);
    }//程序块结束,q被销毁,它指向的内存被释放
    
    int foo = *p;//未定义,p指向的内存已经被释放了
    


    6)其他shared_ptr操作
    可以使用reset来将一个新的指针赋予一个shared_ptr:

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

    与赋值类似,reset会更新引用计数,如果需要的话,会释放p的对象。reset成员经常和unique一起使用,来控制多个shared_ptr共享的对象。在改变底层对象之前,我们检查自己是否是当前对象仅有的用户。如果不是,在改变之前要制作一份新的拷贝:

    f(!p.unique())
    	p.reset(new string(*p));//我们不是唯一用户,分配新的拷贝
    *p+=newVal;//现在我们知道自己是唯一的用户,可以改变对象的值
    


    7)智能指针和异常
    如果使用智能指针,即使程序块过早结束,智能指针也能确保在内存不再需要时将其释放,sp是一个shared_ptr,因此sp销毁时会检测引用计数,当发生异常时,我们直接管理的内存是不会自动释放的。如果使用内置指针管理内存,且在new之后在对应的delete之前发生了异常,则内存不会被释放。

    使用我们自己的释放操作:
    默认情况下,shared_ptr假定他们指向的是动态内存,因此当一个shared_ptr被销毁时,会自动执行delete操作,为了用shared_ptr来管理一个connection,我们必须首先必须定义一个函数来代替delete。这个删除器函数必须能够完成对shared_ptr中保存的指针进行释放的操作。


    8)智能指针陷阱:

    • 不使用相同的内置指针值初始化(或reset)多个智能指针。
    • 不delete get()返回的指针
    • 不使用get()初始化或reset另一个智能指针
    • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了
    • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。


    4、weak_ptr(一种弱引用指针)

    weak_ptr是为了辅助shared_ptr的存在,它只提供了对管理对象的一个访问手段,同时也可以实时动态地知道指向的对象是否存活。

    (只有某个对象的访问权,而没有它的生命控制权 即是 弱引用,所以weak_ptr是一种弱引用型指针)

    (1)内部大概实现

    • 计数区域(SharedPtrControlBlock)结构体引进新的int变量weak_count,来作为弱引用计数。
    • 每个weak_ptr都占指针的两倍空间,一个装着原始指针,一个装着计数区域的指针(和shared_ptr一样的成员)。
    • weak_ptr可以由一个shared_ptr或者另一个weak_ptr构造。
    • weak_ptr的构造和析构不会引起shared_count的增加或减少,只会引起weak_count的增加或减少。

    被管理资源的释放只取决于shared计数,当shared计数为0,才会释放被管理资源,也就是说weak_ptr不控制资源的生命周期
    但是计数区域的释放却取决于shared计数和weak计数,当两者均为0时,才会释放计数区域。

    //shared引用计数和weak引用计数
    //之前的计数区域实际最终应该长这个样子
    struct SharedPtrControlBlock
    {  
       int shared_count;  
       int weak_count;
    };
    //大概长这个样子(简化版)
    template<class T>
    class weak_ptr
    {  
       T* ptr;  
       SharedPtrControlBlock* count;
    };
    

    (2)针对空悬指针问题
    空悬指针问题是指:无法知道指针指向的堆内存是否已经释放。

    得益于引入的weak_count,weak_ptr指针可以使计数区域的生命周期受weak_ptr控制,

    从而能使weak_ptr获取 被管理资源的shared计数,从而判断被管理对象是否已被释放。(可以实时动态地知道指向的对象是否被释放,从而有效解决空悬指针问题)

    它的成员函数**expired()**就是判断指向的对象是否存活。

    (3)针对循环引用问题

    class Monster{
      //尽管父子可以互相访问,但是彼此都是独立的个体,无论是谁都不应该拥有另一个人的所有权。
      std::weak_ptr<Monster> m_father;    //所以都把shared_ptr换成了weak_ptr
      std::weak_ptr<Monster> m_son;      //同上
    
    public:
      void setFather(std::shared_ptr<Monster>& father); //实现细节懒得写了
      void setSon(std::shared_ptr<Monster>& son);    //懒
      ~Monster(){std::cout << "A monster die!";}     //析构时发出死亡的悲鸣
    };
    

    然后执行下面的函数:

    void runGame()
    {  
        std::shared_ptr<Monster> father(new Monster());  
        std::shared_ptr<Monster> son(new Monster());  
        father->setSon(son);  
        son->setFather(father);
    }
    

    那么我们再来模拟一遍,函数退出时栈的shared_ptr对象陆续释放后的情形:

    • 一开始:father指向的堆对象 shared计数为1,weak计数为1;son指向的堆对象 shared计数为1,weak计数为1;
    • son智能指针退出栈:son指向的堆对象 shared计数减为0,weak计数为1,释放son的堆对象;father指向的堆对象 shared计数为1,weak计数减为0;
    • father智能指针退出栈:father指向的堆对象 shared计数减为0,weak计数为0;释放father的堆对象和father的计数区域;son指向的堆对象 shared计数为0,weak计数减为0;释放son的计数区域。
    • 函数结束,释放行为正确。

    (可以说,当生命控制权没有彼此互相掌握时,才能正确解决循环引用问题,而弱引用的使用可以使生命控制权互相掌握的情况消失)

    此外:
    weak_ptr没有重载 * 和 -> ,所以并不能直接使用资源。但可以使用lock()获得一个可用的shared_ptr对象,如果对象已经死了,lock()会失败,返回一个空的shared_ptr。

    void runGame()
    {  
      std::shared_ptr<Monster> monster1(new Monster());   
      std::weak_ptr<Monster> r_monster1 = monster1;  
      r_monster1->doSomething();//Error! 编译器出错!weak_ptr没有重载* 和 -> ,无法直接当指针用     		      std::shared_ptr<Monster> s_monster1 = r_monster1.lock();//OK!可以通过weak_ptr的lock方法获得shared_ptr。
    }
    

    在这里插入图片描述


    5、unique_ptr(一种强引用)

    正如它的名字,独占 是它最大的特点。

    (1)内部大概实现
    它其实算是auto_ptr的翻版(都是独占资源的指针,内部实现也基本差不多).

    但是unique_ptr的名字能更好的体现它的语义,而且在语法上比auto_ptr更安全(尝试复制unique_ptr时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患)

    假如你真的需要转移所有权(独占权),那么你就需要用std::move(std::unique_ptr对象)语法,尽管转移所有权后 还是有可能出现原有指针调用(调用就崩溃)的情况。
    但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。


    (2)示例用法

    void runGame()
    {  
      std::unique_ptr<Monster> monster1(new Monster());//monster1 指向 一个怪物  
      std::unique_ptr<Monster> monster2 = monster1;//Error!编译期出错,不允许复制指针指向同一个资源。     	std::unique_ptr<Monster> monster3 = std::move(monster1);//转移所有权给monster3.  
      monster1->doSomething();//Oops!monster1指向nullptr,运行期崩溃 
    }
    

    (额外:boost库的boost::scoped_ptr也是一个独占性智能指针,但是它不允许转移所有权,从始而终都只对一个资源负责,它更安全谨慎,但是应用的范围也更狭窄。)
    在这里插入图片描述
    虽然我们不能拷贝或者赋值unique_ptr,但是可以通过调用release或reset将指针所有权从一个(非const)unique_ptr转移给另一个unique

    //将所有权从p1(指向string Stegosaurus)转移给p2
    unique_ptr<string> p2(p1.release());//release将p1置为空
    unique_ptr<string>p3(new string("Trex"));
    
    //将所有权从p3转移到p2
    p2.reset(p3.release());//reset释放了p2原来指向的内存
    

    release成员返回unique_ptr当前保存的指针并将其置为空。因此,p2被初始化为p1原来保存的指针,而p1被置为空。

    reset成员接受一个可选的指针参数,令unique_ptr重新指向给定的指针。

    调用release会切断unique_ptr和它原来管理的的对象间的联系。release返回的指针通常被用来初始化另一个智能指针或给另一个智能指针赋值。

    不能拷贝unique_ptr有一个例外:我们可以拷贝或赋值一个将要被销毁的unique_ptr。
    最常见的例子是从函数返回一个unique_ptr:

    unique_ptr<int> clone(int p)
    {
      //正确:从int*创建一个
      unique_ptr<int> return unique_ptr<int>(new int(p));
    }
    

    还可以返回一个局部对象的拷贝:

    unique_ptr<int> clone(int p)
    {
      unique_ptr<int> ret(new int(p));
      return ret;
    }
    

    向后兼容:auto_ptr
    标准库的较早版本包含了一个名为auto_ptr的类,它具有uniqued_ptr的部分特性,但不是全部。

    用unique_ptr传递删除器
    unique_ptr默认使用delete释放它指向的对象,我们可以重载一个unique_ptr中默认的删除器

    我们必须在尖括号中unique_ptr指向类型之后提供删除器类型。在创建或reset一个这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象删除器。


    6、小结

    1)不要使用std::auto_ptr
    2)当你需要一个独占资源所有权(访问权+生命控制权)的指针,且不允许任何外界访问,请使用std::unique_ptr
    3)当你需要一个共享资源所有权(访问权+生命控制权)的指针,请使用std::shared_ptr
    4)当你需要一个能访问资源,但不控制其生命周期的指针,请使用std::weak_ptr

    推荐用法:一个shared_ptr和n个weak_ptr搭配使用 而不是n个shared_ptr
    逻辑上,大部分模型的生命在直观上总是受某一样东西直接控制而不是多样东西共同控制。
    程序上,能够完全避免生命周期互相控制引发的 循环引用问题。

    展开全文
  • C++智能指针

    2018-05-24 22:14:56
    C++ 中推出了强大的智能指针smart_ptr ,本文具体说说 shared_ptr 和 weak_ptr ,特别是 enable_shared_from_this 和 shared_from_this
  • 总结了c++中三种复制控制的场合和具体实现方法,再次基础上设计自己的智能指针类。
  • 主要介绍了C++ 智能指针的模拟实现实例的相关资料,智能指针是一个类,它把普通指针封装起来,能实现和普通指针同样的功能。,需要的朋友可以参考下
  • C++ 智能指针 - 全部用法详解

    千次阅读 多人点赞 2021-07-19 19:43:29
    血的教训?不学智能指针,本人丢了一份工作。

    为什么要学习智能指针?
    咳咳,这个问题不是问大家的,是询问我自己的!
    我依稀记得刚离校出来找实习工作那会,去面试一份工作,其中有一个环节需要答题;有一道题目就是问什么是智能指针?卧槽?当时我就懵逼,智能指针我压根就没有听说过…
    最后,面试的这份工作理所应当的黄了。
    差不多是一年前左右吧,现在趁有闲余时间,学习一下智能指针,丰富一下自己!



    一、为什么要使用智能指针

    一句话带过:智能指针就是帮我们C++程序员管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏

    如下例子就是内存泄露的例子:

    #include <iostream>
    #include <string>
    #include <memory>
    
    using namespace std;
    
    
    // 动态分配内存,没有释放就return
    void memoryLeak1() {
    	string *str = new string("动态分配内存!");
    	return;
    }
    
    // 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了
    int memoryLeak2() {
    	string *str = new string("内存泄露!");
    
    	// ...此处省略一万行代码
    
    	// 发生某些异常,需要结束函数
    	if (1) {
    		return -1;
    	}
    	/
    	// 另外,使用try、catch结束函数,也会造成内存泄漏!
    	/
    
    	delete str;	// 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放
    	return 1;
    }
    
    
    int main(void) {
    
    	memoryLeak1();
    
    	memoryLeak2();
    
    	return 0;
    } 
    

    memoryLeak1函数中,new了一个字符串指针,但是没有delete就已经return结束函数了,导致内存没有被释放,内存泄露!
    memoryLeak2函数中,new了一个字符串指针,虽然在函数末尾有些释放内存的代码delete str,但是在delete之前就已经return了,所以内存也没有被释放,内存泄露!

    使用指针,我们没有释放,就会造成内存泄露。但是我们使用普通对象却不会!

    思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?

    智能指针就是通过这个原理来解决指针自动释放的问题!

    1. C++98 提供了 auto_ptr 模板的解决方案
    2. C++11 增加unique_ptr、shared_ptr 和weak_ptr

    二、auto_ptr

    auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!

    用法:
    头文件: #include < memory >
    用 法: auto_ptr<类型> 变量名(new 类型)

    例 如:
    auto_ptr< string > str(new string(“我要成为大牛~ 变得很牛逼!”));
    auto_ptr<vector< int >> av(new vector< int >());
    auto_ptr< int > array(new int[10]);


    我们先定义一个类,类的构造函数和析构函数都输出一个字符串用作提示!
    定义一个私有成员变量,赋值20.
    再定义一个私有成员方法用于返回这个私有成员变量。

    class Test {
    public:
    	Test() { cout << "Test的构造函数..." << endl; }
    	~Test() { cout << "Test的析构函数..." << endl; }
    
    	int getDebug() { return this->debug; }
    
    private:
    	int debug = 20;
    };
    

    当我们直接new这个类的对象,却没有释放时。。。

    int main(void) {
    	Test *test = new Test;
    
    	return 0;
    } 
    

    在这里插入图片描述

    可以看到,只是打印了构造函数这个字符串,而析构函数的字符却没有被打印,说明并没有调用析构函数!这就导致了内存泄露!
    解决内存泄露的办法,要么手动delete,要么使用智能指针!

    使用智能指针

    // 定义智能指针
    auto_ptr<Test> test(new Test);
    

    智能指针可以像普通指针那样使用:

    cout << "test->debug:" << test->getDebug() << endl;
    cout << "(*test).debug:" << (*test).getDebug() << endl;
    

    这时再试试:

    int main(void) {
    
    	//Test *test = new Test;
    	auto_ptr<Test> test(new Test);
    
    	cout << "test->debug:" << test->getDebug() << endl;
    	cout << "(*test).debug:" << (*test).getDebug() << endl;
    
    	return 0;
    } 
    

    在这里插入图片描述
    自动调用了析构函数。
    为什么智能指针可以像普通指针那样使用???
    因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。
    在这里插入图片描述
    具体原因不用深究,只需知道他为什么可以这样操作就像!
    函数中返回的是调用get()方法返回的值,那么这个get()是什么呢?

    智能指针的三个常用函数:

    1. get() 获取智能指针托管的指针地址

      // 定义智能指针
      auto_ptr<Test> test(new Test);
      
      Test *tmp = test.get();		// 获取指针返回
      cout << "tmp->debug:" << tmp->getDebug() << endl;
      

      但我们一般不会这样使用,因为都可以直接使用智能指针去操作,除非有一些特殊情况。
      函数原型

      _NODISCARD _Ty * get() const noexcept
      {	// return wrapped pointer
      	return (_Myptr);
      }
      
    2. release() 取消智能指针对动态内存的托管

      // 定义智能指针
      auto_ptr<Test> test(new Test);
      
      Test *tmp2 = test.release();	// 取消智能指针对动态内存的托管
      delete tmp2;	// 之前分配的内存需要自己手动释放
      

      也就是智能指针不再对该指针进行管理,改由管理员进行管理!
      函数原型

      _Ty * release() noexcept
      {	// return wrapped pointer and give up ownership
      	_Ty * _Tmp = _Myptr;
      	_Myptr = nullptr;
      	return (_Tmp);
      }
      
    3. reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉

      // 定义智能指针
      auto_ptr<Test> test(new Test);
      
      test.reset();			// 释放掉智能指针托管的指针内存,并将其置NULL
      
      test.reset(new Test());	// 释放掉智能指针托管的指针内存,并将参数指针取代之
      

      reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。
      函数原型:

      void reset(_Ty * _Ptr = nullptr)
      {	// destroy designated object and store new pointer
      	if (_Ptr != _Myptr)
      		delete _Myptr;
      	_Myptr = _Ptr;
      }
      

    使用建议

    1. 尽可能不要将auto_ptr 变量定义为全局变量或指针;

      // 没有意义,全局变量也是一样
      auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);	
      
    2. 除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针;

      auto_ptr<Test> t1(new Test);
      auto_ptr<Test> t2(new Test);
      t1 = t2;	// 不要这样操作...
      
    3. C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!C++11后不建议使用auto_ptr。

    4. auto_ptr 被C++11抛弃的主要原因

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

      // auto_ptr 被C++11抛弃的主要原因
      auto_ptr<string> p1(new string("I'm Li Ming!"));
      auto_ptr<string> p2(new string("I'm age 22."));
      
      cout << "p1:" << p1.get() << endl;
      cout << "p2:" << p2.get() << endl;
      
      // p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针,
      // 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。
      p1 = p2;	
      cout << "p1 = p2 赋值后:" << endl;
      cout << "p1:" << p1.get() << endl;
      cout << "p2:" << p2.get() << endl;
      

      在这里插入图片描述

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

      vector<auto_ptr<string>> vec;
      auto_ptr<string> p3(new string("I'm P3"));
      auto_ptr<string> p4(new string("I'm P4"));
      
      // 必须使用std::move修饰成右值,才可以进行插入容器中
      vec.push_back(std::move(p3));
      vec.push_back(std::move(p4));
      
      cout << "vec.at(0):" <<  *vec.at(0) << endl;
      cout << "vec[1]:" <<  *vec[1] << endl;
      
      
      // 风险来了:
      vec[0] = vec[1];	// 如果进行赋值,问题又回到了上面一个问题中。
      cout << "vec.at(0):" << *vec.at(0) << endl;
      cout << "vec[1]:" << *vec[1] << endl;
      

      访问越界了!
      在这里插入图片描述

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

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

      在这里插入图片描述

    所以,C++11用更严谨的unique_ptr 取代了auto_ptr!

    测试代码

    #include <iostream>
    #include <string>
    #include <memory>
    #include <vector>
    
    using namespace std;
    
    class Test {
    public:
    	Test() { cout << "Test的构造函数..." << endl; }
    	~Test() { cout << "Test的析构函数..." << endl; }
    
    	int getDebug() { return this->debug; }
    
    private:
    	int debug = 20;
    };
    
    // 不要定义为全局变量,没有意义
    //auto_ptr<Test> test(new Test);
    
    void memoryLeak1() {
    	//Test *test = new Test;
    
    	// 定义智能指针
    	auto_ptr<Test> test(new Test);
    	
    	cout << "test->debug:" << test->getDebug() << endl;
    	cout << "(*test).debug:" << (*test).getDebug() << endl;
    
    
    	// get方法
    	Test *tmp = test.get();		// 获取指针返回
    	cout << "tmp->debug:" << tmp->getDebug() << endl;
    
    
    	// release方法
    	Test *tmp2 = test.release();	// 取消智能指针对动态内存的托管
    	delete tmp2;	// 之前分配的内存需要自己手动释放
    
    
    	// reset方法:重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
    	test.reset();			// 释放掉智能指针托管的指针内存,并将其置NULL
    	test.reset(new Test());	// 释放掉智能指针托管的指针内存,并将参数指针取代之
    
    
    	// 忠告:不要将智能指针定义为指针
    	//auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
    
    	// 忠告:不要定义指向智能指针对象的指针变量
    	//auto_ptr<Test> t1(new Test);
    	//auto_ptr<Test> t2(new Test);
    	//t1 = t2;
    
    	return;
    }
    
    int memoryLeak2() {
    	//Test *test = new Test();
    
    	// 定义智能指针
    	auto_ptr<Test> test(new Test);
    
    	// ...此处省略一万行代码
    
    	// 发生某些异常,需要结束函数
    	if (1) {
    		return -1;
    	}
    
    	//delete test;
    	return 1;
    }
    
    
    int main1(void) {
    
    	//memoryLeak1();
    
    	//memoryLeak2();
    
    	//Test *test = new Test;
    	//auto_ptr<Test> test(new Test);
    
    	//cout << "test->debug:" << test->getDebug() << endl;
    	//cout << "(*test).debug:" << (*test).getDebug() << endl;
    
    
    	 auto_ptr 被C++11抛弃的主要原因
    	//auto_ptr<string> p1(new string("I'm Li Ming!"));
    	//auto_ptr<string> p2(new string("I'm age 22."));
    	//
    	//cout << "p1:" << p1.get() << endl;
    	//cout << "p2:" << p2.get() << endl;
    
    	//p1 = p2;
    	//cout << "p1 = p2 赋值后:" << endl;
    	//cout << "p1:" << p1.get() << endl;
    	//cout << "p2:" << p2.get() << endl;
    
    
    
    	// 弊端2.在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制
    	vector<auto_ptr<string>> vec;
    	auto_ptr<string> p3(new string("I'm P3"));
    	auto_ptr<string> p4(new string("I'm P4"));
    
    	vec.push_back(std::move(p3));
    	vec.push_back(std::move(p4));
    
    	cout << "vec.at(0):" <<  *vec.at(0) << endl;
    	cout << "vec[1]:" <<  *vec[1] << endl;
    
    
    	// 风险来了:
    	vec[0] = vec[1];
    	cout << "vec.at(0):" << *vec.at(0) << endl;
    	cout << "vec[1]:" << *vec[1] << endl;
    
    
    	// 弊端3.不支持对象数组的内存管理
    	//auto_ptr<int[]> array(new int[5]);	// 不能这样定义
    	return 0;
    } 
    

    三、unique_ptr

    auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr 主要有三大问题:

    1. 复制和赋值会改变资源的所有权,不符合人的直觉。
    2. 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
    3. 不支持对象数组的操作

    以上问题已经在上面体现出来了,下面将使用unique_ptr解决这些问题。

    所以,C++11用更严谨的unique_ptr 取代了auto_ptr!

    unique_ptr 和 auto_ptr用法几乎一样,除了一些特殊。

    unique_ptr特性

    1. 基于排他所有权模式:两个指针不能指向同一个资源
    2. 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
    3. 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
    4. 在容器中保存指针是安全的

    A. 无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值

    unique_ptr<string> p1(new string("I'm Li Ming!"));
    unique_ptr<string> p2(new string("I'm age 22."));
    	
    cout << "p1:" << p1.get() << endl;
    cout << "p2:" << p2.get() << endl;
    
    p1 = p2;					// 禁止左值赋值
    unique_ptr<string> p3(p2);	// 禁止左值赋值构造
    
    unique_ptr<string> p3(std::move(p1));
    p1 = std::move(p2);	// 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
    
    cout << "p1 = p2 赋值后:" << endl;
    cout << "p1:" << p1.get() << endl;
    cout << "p2:" << p2.get() << endl;
    

    在这里插入图片描述

    运行截图:
    在这里插入图片描述

    B. 在 STL 容器中使用unique_ptr,不允许直接赋值

    vector<unique_ptr<string>> vec;
    unique_ptr<string> p3(new string("I'm P3"));
    unique_ptr<string> p4(new string("I'm P4"));
    
    vec.push_back(std::move(p3));
    vec.push_back(std::move(p4));
    
    cout << "vec.at(0):" << *vec.at(0) << endl;
    cout << "vec[1]:" << *vec[1] << endl;
    
    vec[0] = vec[1];	/* 不允许直接赋值 */
    vec[0] = std::move(vec[1]);		// 需要使用move修饰,使得程序员知道后果
    
    cout << "vec.at(0):" << *vec.at(0) << endl;
    cout << "vec[1]:" << *vec[1] << endl;
    

    在这里插入图片描述

    当然,运行后是直接报错的,因为vec[1]已经是NULL了,再继续访问就越界了。

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

    // 会自动调用delete [] 函数去释放内存
    unique_ptr<int[]> array(new int[5]);	// 支持这样定义
    

    除了上面ABC三项外,unique_ptr的其余用法都与auto_ptr用法一致。

    1. 构造

      class Test {
      public:
      	Test() { cout << "Test的构造函数..." << endl; }
      	~Test() { cout << "Test的析构函数..." << endl; }
      
      	void doSomething() { cout << "do something......" << endl; }
      };
      
      
      // 自定义一个内存释放其
      class DestructTest {
      	public:
      	void operator()(Test *pt) {
      		pt->doSomething();
      		delete pt;
      	}
      };
      
      // unique_ptr<T> up; 空的unique_ptr,可以指向类型为T的对象
      unique_ptr<Test> t1;
      
      // unique_ptr<T> up1(new T());	定义unique_ptr,同时指向类型为T的对象
      unique_ptr<Test> t2(new Test);
      
      // unique_ptr<T[]> up;	空的unique_ptr,可以指向类型为T[的数组对象
      unique_ptr<int[]> t3;
      
      // unique_ptr<T[]> up1(new T[]);	定义unique_ptr,同时指向类型为T的数组对象
      unique_ptr<int[]> t4(new int[5]);
      
      // unique_ptr<T, D> up();	空的unique_ptr,接受一个D类型的删除器D,使用D释放内存
      unique_ptr<Test, DestructTest> t5;
      
      // unique_ptr<T, D> up(new T());	定义unique_ptr,同时指向类型为T的对象,接受一个D类型的删除器D,使用删除器D来释放内存
      unique_ptr<Test, DestructTest> t6(new Test);
      
    2. 赋值

      unique_ptr<Test> t7(new Test);
      unique_ptr<Test> t8(new Test);
      t7 = std::move(t8);	// 必须使用移动语义,结果,t7的内存释放,t8的内存交给t7管理
      t7->doSomething();
      
    3. 主动释放对象

      unique_ptr<Test> t9(new Test);
      t9 = NULL;
      t9 = nullptr;
      t9.reset();
      
    4. 放弃对象的控制权

      Test *t10 = t9.release();
      
    5. 重置

      t9.reset(new Test);
      

    auto_ptr 与 unique_ptr智能指针的内存管理陷阱

    auto_ptr<string> p1;
    string *str = new string("智能指针的内存管理陷阱");
    p1.reset(str);	// p1托管str指针
    {
    	auto_ptr<string> p2;
    	p2.reset(str);	// p2接管str指针时,会先取消p1的托管,然后再对str的托管
    }
    
    // 此时p1已经没有托管内容指针了,为NULL,在使用它就会内存报错!
    cout << "str:" << *p1 << endl;
    

    在这里插入图片描述
    这是由于auto_ptr 与 unique_ptr的排他性所导致的!
    为了解决这样的问题,我们可以使用shared_ptr指针指针!


    四、shared_ptr

    熟悉了unique_ptr 后,其实我们发现unique_ptr 这种排他型的内存管理并不能适应所有情况,有很大的局限!如果需要多个指针变量共享怎么办?

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

    在这里插入图片描述

    class Person {
    public:
    	Person(int v) {
    		this->no = v;
    		cout << "构造函数 \t no = " << this->no << endl;
    	}
    
    	~Person() {
    		cout << "析构函数 \t no = " << this->no << endl;
    	}
    
    private:
    	int no;
    };
    
    // 仿函数,内存删除
    class DestructPerson {
    public:
    	void operator() (Person *pt) {
    		cout << "DestructPerson..." << endl;
    		delete pt;
    	}
    };
    
    1. 引用计数的使用

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

      shared_ptr<Person> sp1;
      
      shared_ptr<Person> sp2(new Person(2));
      
      // 获取智能指针管控的共享指针的数量	use_count():引用计数
      cout << "sp1	use_count() = " << sp1.use_count() << endl;
      cout << "sp2	use_count() = " << sp2.use_count() << endl << endl;
      
      // 共享
      sp1 = sp2;
      
      cout << "sp1	use_count() = " << sp1.use_count() << endl;
      cout << "sp2	use_count() = " << sp2.use_count() << endl << endl;
      
      shared_ptr<Person> sp3(sp1);
      cout << "sp1	use_count() = " << sp1.use_count() << endl;
      cout << "sp2	use_count() = " << sp2.use_count() << endl;
      cout << "sp2	use_count() = " << sp3.use_count() << endl << endl;
      

      如上代码,sp1 = sp2; 和 shared_ptr< Person > sp3(sp1);就是在使用引用计数了。

      sp1 = sp2; --> sp1和sp2共同托管同一个指针,所以他们的引用计数为2;
      shared_ptr< Person > sp3(sp1); --> sp1和sp2和sp3共同托管同一个指针,所以他们的引用计数为3;
      在这里插入图片描述

    2. 构造

      1). shared_ptr< T > sp1; 空的shared_ptr,可以指向类型为T的对象

      shared_ptr<Person> sp1;
      Person *person1 = new Person(1);
      sp1.reset(person1);	// 托管person1
      

      2). shared_ptr< T > sp2(new T()); 定义shared_ptr,同时指向类型为T的对象

      shared_ptr<Person> sp2(new Person(2));
      shared_ptr<Person> sp3(sp1);
      

      3). shared_ptr<T[]> sp4; 空的shared_ptr,可以指向类型为T[]的数组对象 C++17后支持

      shared_ptr<Person[]> sp4;
      

      4). shared_ptr<T[]> sp5(new T[] { … }); 指向类型为T的数组对象 C++17后支持

      shared_ptr<Person[]> sp5(new Person[5] { 3, 4, 5, 6, 7 });
      

      5). shared_ptr< T > sp6(NULL, D()); //空的shared_ptr,接受一个D类型的删除器,使用D释放内存

      shared_ptr<Person> sp6(NULL, DestructPerson());
      

      6). shared_ptr< T > sp7(new T(), D()); //定义shared_ptr,指向类型为T的对象,接受一个D类型的删除器,使用D删除器来释放内存

      shared_ptr<Person> sp7(new Person(8), DestructPerson());
      
    3. 初始化

      1). 方式一:构造函数

      shared_ptr<int> up1(new int(10));  // int(10) 的引用计数为1
      shared_ptr<int> up2(up1);  // 使用智能指针up1构造up2, 此时int(10) 引用计数为2
      

      2). 方式二:使用make_shared 初始化对象,分配内存效率更高(推荐使用)
      make_shared函数的主要功能是在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr; 用法:
      make_shared<类型>(构造类型对象需要的参数列表);

      shared_ptr<int> up3 = make_shared<int>(2); // 多个参数以逗号','隔开,最多接受十个
      shared_ptr<string> up4 = make_shared<string>("字符串");
      shared_ptr<Person> up5 = make_shared<Person>(9);
      
    4. 赋值

      shared_ptrr<int> up1(new int(10));  // int(10) 的引用计数为1
      shared_ptr<int> up2(new int(11));   // int(11) 的引用计数为1
      up1 = up2;	// int(10) 的引用计数减1,计数归零内存释放,up2共享int(11)给up1, int(11)的引用计数为2
      
    5. 主动释放对象

      shared_ptrr<int> up1(new int(10));
      up1 = nullptr ;	// int(10) 的引用计数减1,计数归零内存释放 
      // 或
      up1 = NULL; // 作用同上 
      
    6. 重置
      p.reset() ; 将p重置为空指针,所管理对象引用计数 减1
      p.reset(p1); 将p重置为p1(的值),p 管控的对象计数减1,p接管对p1指针的管控
      p.reset(p1,d); 将p重置为p1(的值),p 管控的对象计数减1并使用d作为删除器
      p1是一个指针!

    7. 交换
      p1 和 p2 是智能指针

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

    shared_ptr使用陷阱

    shared_ptr作为被管控的对象的成员时,小心因循环引用造成无法释放资源!

    如下代码:
    Boy类中有Girl的智能指针;
    Girl类中有Boy的智能指针;
    当他们交叉互相持有对方的管理对象时…

    #include <iostream>
    #include <string>
    #include <memory>
    
    using namespace std;
    
    class Girl;
    
    class Boy {
    public:
    	Boy() {
    		cout << "Boy 构造函数" << endl;
    	}
    
    	~Boy() {
    		cout << "~Boy 析构函数" << endl;
    	}
    
    	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
    		this->girlFriend = _girlFriend;
    	}
    
    private:
    	shared_ptr<Girl> girlFriend;
    };
    
    class Girl {
    public:
    	Girl() {
    		cout << "Girl 构造函数" << endl;
    	}
    
    	~Girl() {
    		cout << "~Girl 析构函数" << endl;
    	}
    
    	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
    		this->boyFriend = _boyFriend;
    	}
    
    private:
    	shared_ptr<Boy> boyFriend;
    };
    
    
    void useTrap() {
    	shared_ptr<Boy> spBoy(new Boy());
    	shared_ptr<Girl> spGirl(new Girl());
    
    	// 陷阱用法
    	spBoy->setGirlFriend(spGirl);
    	spGirl->setBoyFriend(spBoy);
    	// 此时boy和girl的引用计数都是2
    }
    
    
    int main(void) {
    	useTrap();
    
    	system("pause");
    	return 0;
    }
    

    运行截图:

    在这里插入图片描述

    可以看出,程序结束了,但是并没有释放内存,这是为什么呢???

    如下图:
    当我们执行useTrap函数时,注意,是没有结束此函数,boy和girl指针其实是被两个智能指针托管的,所以他们的引用计数是2
    在这里插入图片描述

    useTrap函数结束后,函数中定义的智能指针被清掉,boy和girl指针的引用计数减1,还剩下1,对象中的智能指针还是托管他们的,所以函数结束后没有将boy和gilr指针释放的原因就是于此。
    在这里插入图片描述

    所以在使用shared_ptr智能指针时,要注意避免对象交叉使用智能指针的情况! 否则会导致内存泄露!

    当然,这也是有办法解决的,那就是使用weak_ptr弱指针。

    针对上面的情况,还讲一下另一种情况。如果是单方获得管理对方的共享指针,那么这样着是可以正常释放掉的!
    例如:

    void useTrap() {
    	shared_ptr<Boy> spBoy(new Boy());
    	shared_ptr<Girl> spGirl(new Girl());
    
    	// 单方获得管理
    	//spBoy->setGirlFriend(spGirl);
    	spGirl->setBoyFriend(spBoy);	
    }
    

    在这里插入图片描述
    反过来也是一样的!

    这是什么原理呢?

    1. 首先释放spBoy,但是因为girl对象里面的智能指针还托管着boy,boy的引用计数为2,所以释放spBoy时,引用计数减1,boy的引用计数为1;
    2. 在释放spGirl,girl的引用计数减1,为零,开始释放girl的内存,因为girl里面还包含有托管boy的智能指针对象,所以也会进行boyFriend的内存释放,boy的引用计数减1,为零,接着开始释放boy的内存。最终所有的内存都释放了。

    五、weak_ptr

    weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 同时weak_ptr 没有重载*和->但可以使用 lock 获得一个可用的 shared_ptr 对象。

    1. 弱指针的使用;
      weak_ptr wpGirl_1; // 定义空的弱指针
      weak_ptr wpGirl_2(spGirl); // 使用共享指针构造
      wpGirl_1 = spGirl; // 允许共享指针赋值给弱指针

    2. 弱指针也可以获得引用计数;
      wpGirl_1.use_count()

    3. 弱指针不支持 * 和 -> 对指针的访问;
      在这里插入图片描述

    4. 在必要的使用可以转换成共享指针 lock();

      shared_ptr<Girl> sp_girl;
      sp_girl = wpGirl_1.lock();
      
      // 使用完之后,再将共享指针置NULL即可
      sp_girl = NULL;
      

    使用代码:

    shared_ptr<Boy> spBoy(new Boy());
    shared_ptr<Girl> spGirl(new Girl());
    
    // 弱指针的使用
    weak_ptr<Girl> wpGirl_1;			// 定义空的弱指针
    weak_ptr<Girl> wpGirl_2(spGirl);	// 使用共享指针构造
    wpGirl_1 = spGirl;					// 允许共享指针赋值给弱指针
    
    cout << "spGirl \t use_count = " << spGirl.use_count() << endl;
    cout << "wpGirl_1 \t use_count = " << wpGirl_1.use_count() << endl;
    
    	
    // 弱指针不支持 * 和 -> 对指针的访问
    /*wpGirl_1->setBoyFriend(spBoy);
    (*wpGirl_1).setBoyFriend(spBoy);*/
    
    // 在必要的使用可以转换成共享指针
    shared_ptr<Girl> sp_girl;
    sp_girl = wpGirl_1.lock();
    
    cout << sp_girl.use_count() << endl;
    // 使用完之后,再将共享指针置NULL即可
    sp_girl = NULL;
    

    当然这只是一些使用上的小例子,具体用法如下:

    请看Boy类

    #include <iostream>
    #include <string>
    #include <memory>
    
    using namespace std;
    
    class Girl;
    
    class Boy {
    public:
    	Boy() {
    		cout << "Boy 构造函数" << endl;
    	}
    
    	~Boy() {
    		cout << "~Boy 析构函数" << endl;
    	}
    
    	void setGirlFriend(shared_ptr<Girl> _girlFriend) {
    		this->girlFriend = _girlFriend;
    
    
    		// 在必要的使用可以转换成共享指针
    		shared_ptr<Girl> sp_girl;
    		sp_girl = this->girlFriend.lock();
    
    		cout << sp_girl.use_count() << endl;
    		// 使用完之后,再将共享指针置NULL即可
    		sp_girl = NULL;
    	}
    
    private:
    	weak_ptr<Girl> girlFriend;
    };
    
    class Girl {
    public:
    	Girl() {
    		cout << "Girl 构造函数" << endl;
    	}
    
    	~Girl() {
    		cout << "~Girl 析构函数" << endl;
    	}
    
    	void setBoyFriend(shared_ptr<Boy> _boyFriend) {
    		this->boyFriend = _boyFriend;
    	}
    
    private:
    	shared_ptr<Boy> boyFriend;
    };
    
    
    void useTrap() {
    	shared_ptr<Boy> spBoy(new Boy());
    	shared_ptr<Girl> spGirl(new Girl());
    
    	spBoy->setGirlFriend(spGirl);
    	spGirl->setBoyFriend(spBoy);
    }
    
    
    int main(void) {
    	useTrap();
    
    	system("pause");
    	return 0;
    }
    

    在这里插入图片描述

    在类中使用弱指针接管共享指针,在需要使用时就转换成共享指针去使用即可!

    自此问题完美解决!

    expired函数的用法
    应评论区某位朋友的要求,现在加上weak_ptr指针的expired函数的用法!

    expired:判断当前weak_ptr智能指针是否还有托管的对象,有则返回false,无则返回true

    如果返回true,等价于 use_count() == 0,即已经没有托管的对象了;当然,可能还有析构函数进行释放内存,但此对象的析构已经临近(或可能已发生)。

    示例
    演示如何用 expired 检查指针的合法性。
    在网上找了一段代码,加上自己的注释理解

    #include <iostream>
    #include <memory>
    
    std::weak_ptr<int> gw;
    
    void f() {
    
    	// expired:判断当前智能指针是否还有托管的对象,有则返回false,无则返回true
    	if (!gw.expired()) {
    		std::cout << "gw is valid\n";	// 有效的,还有托管的指针
    	} else {
    		std::cout << "gw is expired\n";	// 过期的,没有托管的指针
    	}
    }
    
    int main() {
    	{
    		auto sp = std::make_shared<int>(42);
    		gw = sp;
    
    		f();
    	}
    
    	// 当{ }体中的指针生命周期结束后,再来判断其是否还有托管的指针
    	f();
    
    	return 0;
    }
    

    在这里插入图片描述

    在 { } 中,gw的生命周期还在,他还在托管着make_shared赋值的指针,所以调用f()函数时打印"gw is valid\n";
    当执行完 { } 后,gw的生命周期已经结束,已经调用析构函数释放make_shared指针内存,gw已经没有在托管任何指针了,调用expired()函数返回true,所以打印"gw is expired\n";


    六、智能指针的使用陷阱

    1. 不要把一个原生指针给多个智能指针管理;

      int *x = new int(10);
      unique_ptr< int > up1(x);
      unique_ptr< int > up2(x);
      // 警告! 以上代码使up1 up2指向同一个内存,非常危险
      或以下形式:
      up1.reset(x);
      up2.reset(x);

    2. 记得使用u.release()的返回值;
      在调用u.release()时是不会释放u所指的内存的,这时返回值就是对这块内存的唯一索引,如果没有使用这个返回值释放内存或是保存起来,这块内存就泄漏了.

    3. 禁止delete 智能指针get 函数返回的指针;
      如果我们主动释放掉get 函数获得的指针,那么智能 指针内部的指针就变成野指针了,析构时造成重复释放,带来严重后果!

    4. 禁止用任何类型智能指针get 函数返回的指针去初始化另外一个智能指针!
      shared_ptr< int > sp1(new int(10));
      // 一个典型的错误用法 shared_ptr< int > sp4(sp1.get());


    七、总结

    智能指针虽然使用起来很方便,但是要注意使用智能指针的一些陷阱,否则会造成严重的内存报错或者内存泄露等问题!

    展开全文
  • shared_ptr在boost中地位相当重要,其行为接近原始指针,但又比指针更加安全,甚至还能提供基本的线程安全保证。它基本上解决了在使用c++开发过程中不可避免的使用指针而遇到的许多问题,常见的毫无疑问是内存泄漏和...
  • 智能指针

    千次阅读 2021-03-05 17:29:16
    一、为什么使用智能指针 当我们使用new操作,分配了堆中的内存,但是忘了delete释放掉分配的内存,此时就会造成内存泄漏 如果我们记得delete释放内存,也有可能存在内存泄露的问题,比如: void remodel(std::string...

    一、为什么使用智能指针
    当我们使用new操作,分配了堆中的内存,但是忘了delete释放掉分配的内存,此时就会造成内存泄漏
    如果我们记得delete释放内存,也有可能存在内存泄露的问题,比如:

    void remodel(std::string & str){
    	std::string * ps = new std::string(str);
    	...
    	if(weird_thing())
    		throw exception();
    	str = *ps;
    	delete ps;
    	return;
    }
    

    那么当出现异常的时候,delete将不会被执行,因此也将导致内存泄漏

    二、智能指针的定义
    智能指针的行为类似于指针的类对象,但是这种对象还有其它功能。
    思想:如果是指针它是一个对象,那么在对象过期的时候,它的析构函数就会自动删除所指向的内存。
    有三个功能的智能指针 auto_ptr(C98提出的)、unique_ptr、shared_ptr(C11)
    这三个都定义了类似指针的对象,可以将new获得(直接或者间接)的地址赋给这种对象。
    当智能指针过期的时候,其析构函数将使用delete来释放内存。
    shared_ptr:允许多个指针指向同一个对象
    unique_ptr:则独占所指向的对象
    标准库还定义了一个名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象。
    这三种类型都定义在memory头文件

    三、智能指针的原理
    (1)关于智能指针类型shared_ptr的计数问题
    1、关键
    每个shared_ptr所指向的对象都有一个引用计数,它记录了有多少个shared_ptr指向自己;
    shared_ptr的析构函数:递减它所指向的对象的引用计数,如果引用计数变为0,就会销毁对象并释放相应的内存;
    引用计数的变化:决定权在shared_ptr,而与对象本身无关
    2、引用计数增加的情况
    拷贝一个shared_ptr,其所指对象的引用计数会递增,如:
    ①用一个shared_ptr初始化另一个shared_ptr
    ②用一个shared_ptr给另一个shared_ptr赋值
    ③将shared_ptr作为参数的返回值
    3、引用计数减少的情况
    ①给shared_ptr赋予一个新值
    ②shared_ptr被销毁(如离开作用域)
    4、例子

    #include<iostream>
    #include<memory>
    using namespace std;
    int main() {
    	shared_ptr<int>sp;
    	shared_ptr<int>sp2 = make_shared<int>(3);
    	shared_ptr<int>sp3(sp2);
    	cout << sp.use_count() << endl;//输出0
    	cout << sp2.use_count() << endl;//输出2
    	cout << sp3.use_count() << endl;//输出2
    }
    
    #include<iostream>
    #include<memory>
    using namespace std;
    int main() {
    	shared_ptr<int>sp;
    	shared_ptr<int>sp2 = make_shared<int>(3);
    	shared_ptr<int>sp3(sp2);
    	sp3 = make_shared<int>(4);
    	cout << sp.use_count() << endl;//输出0
    	cout << sp2.use_count() << endl;//输出1
    	cout << sp3.use_count() << endl;//输出1
    }
    

    (2)unique_ptr类型
    1、定义
    不像shared_ptr那样,它没有make_shared的标准库返回一个unique_ptr。
    当定义一个unique_ptr时,需要将其绑定到一个new返回的指针上。

    unique_ptr<double>p1;//可以指向一个double的unique_ptr
    unique_ptr<int>p2(new int(42));//p2指向一个值为42的int
    

    由于一个unique_ptr拥有它指向的对象,因此unique_ptr不支持普通的拷贝或赋值操作:

    unique_ptr<string>p1(new string("Stegosaurus"));
    unique_ptr<string>p2(p1);//错误:unique_ptr不支持拷贝
    unique_ptr<string>p3;
    p3=p2;//错误:unique_ptr不支持赋值
    

    (3)weak_ptr类型
    weak_ptr是一种不控制所指向对象生存期的智能指针;
    它指向由一个shared_ptr管理的对象;
    将一个wear_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数;
    一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放;
    即使有weak_ptr指向对象,对象也还是会被释放;
    因此,weak_ptr的名字抓住了这种智能指针“弱”共享对象的特点。
    1、定义:

    auto p= make_shared_ptr<int>(42);
    weak_ptr<int>wp(p);//wp弱共享p;p的引用计数未改变
    

    ①本例中wp和p指向相同的对象。由于是弱共享,创建wp不会改变p的引用计数;
    wp指向的对象可能被释放掉。
    ②由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock。
    此函数用于检查weak_ptr指向的对象是否存在。
    如果存在,lock返回一个指向共享对象的shared_ptr。
    与任何其他shared_ptr类似。只要此shared_ptr存在,它所指向的底层对象也就会一直存在。例如:

    if(shared_ptr<int>np=wp.lock()){//如果np不为空则条件成立
    //在if中,np与p共享对象
    }
    
    展开全文
  • 一个具有指针成员的类,如果发生复制行为,一个指针复制到另一个指针时,两个指针指向同一个对象。此时可以使用任一指针改变这个共享的对象。那么,如果一个指针删除了这个共享对象,那么另一指针成了悬垂指针,如果...
  • 智能指针的简单实现

    2018-05-03 15:25:20
    这是一个c++的智能指针,可以用c++的map ,vector等等 用起来还是比较简单的包含一个头文件就可以了

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 104,491
精华内容 41,796
关键字:

智能指针