zoukankan      html  css  js  c++  java
  • 智能指针

    首先为什么需要提出智能指针的概念,先看下这个例子

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

    如果weird_thing()返回1,程序直接抛出异常,那么ps这个指针便不能被执行到,当程序执行完之后,ps指针本身所占的内存会被释放,但是ps所指的内存将不被释放。这就是所谓的内存泄漏,指针没了,但是所指向的内存没有被释放。

    我们可以想到,如果ps有一个析构函数,该析构函数将在ps过期时自动释放它指向的内存。但ps的问题在于,它只是一个常规指针,不是有析构凼数的类对象指针。如果它指向的是对象,则可以在对象过期时,让它的析构函数删除指向的内存。

    将基本类型指针封装为类对象指针(这个类肯定是个模板,以适应不同基本类型的需求),并在析构函数里编写delete语句删除指针指向的内存空间。

    因此,要转换remodel()函数,应按下面3个步骤进行:

    • 包含头义件memory(智能指针所在的头文件);
    • 将指向string的指针替换为指向string的智能指针对象;
    • 删除delete语句。
    # include <memory>
    void remodel (std::string & str)
    {
        std::auto_ptr<std::string> ps (new std::string(str));
        ...
        if (weird_thing ())
            throw exception(); 
        str = *ps; 
        // delete ps; NO LONGER NEEDED
        return;
    }

    (1)auto_ptr

    templet<class T>
    class auto_ptr {
      explicit auto_ptr(X* p = 0) ; 
      ...
    };
    shared_ptr<double> pd; 
    double *p_reg = new double;
    pd = p_reg;                               // not allowed (implicit conversion)
    pd = shared_ptr<double>(p_reg);           // allowed (explicit conversion)
    shared_ptr<double> pshared = p_reg;       // not allowed (implicit conversion)
    shared_ptr<double> pshared(p_reg);        // allowed (explicit conversion)

    将常规类型的指针可以显示转换成智能指针,可以通过强制类型转换和传参两种形式,如上面所写的形式。

    缺点:

    1、不要使用auto_ptr保存一个非动态开辟空间的指针,因为在作用域结束的时候,会执行智能指针的析构函数,释放这块空间,但非动态的空间又无法释放;

    //正确情况:
    int i=new int(1);   //堆上的空间——动态开辟
    auto_ptr<int> ap1(&i);
    //错误情况:
    int i=1;  //栈上的空间
    auot_ptr<int> ap2(&i);

    2、不要使用两个auto_ptr指针指向同一个指针。因为如果是常规指针,就相当于两个指针指向同一个对象。但是如果是auto_ptr来说

    int a=10int *p=&a; 
    auto_ptr p1(p); 
    auto_ptr p2(p1); 

    此时,p1这个智能指针就无法管理p这块内存内存空间了,因为在auto_ptr的拷贝函数的定义中,将p1拷贝给p2之后,p1就是个空指针了,这样执行析构函数的时候,还会对p1进行析构,即使此时p1保存的是一个空指针,但是释放造成的开销也是不必要的

    3、不要使用auto_ptr指向一个指针数组,因为auto_ptr的析构函数所用的是delete而不是delete[],不匹配;

    4、不要将auto_ptr储存在容器中,因为赋值和拷贝构造后原指针无法使用。 

    (2)scoped_ptr

    scoped_ptr没有给出拷贝构造和赋值运算符的重载运算符的定义,只给了private下的声明,即表明scoped_ptr智能指针无法使用一个对象创建另一个对象,也无法采用赋值的形式。这无疑提升了智能指针的安全性,但是又存在无法“++”、“–”这些操作,当然也多了“*”、“->”这两种操作。所以这种形式也并不是最完美的。所以又有了shared_ptr。

    (3)shared_ptr

    shared_ptr和以上二者的最大区别就是维护了一个引用计数,用于检测当前对象所管理的指针是否还被其他智能指针使用(必须是shared_ptr管理的智能指针),在析构函数时对其引用计数减一,判断是否为0,若为0,则释放这个指针和这个引用计数的空间。其实,这个原理就和string类的浅拷贝是一样的。 

    缺陷:当管理的每一个指针都是一个双向链表的指针时,那么此时我们的析构函数存在一个很大的问题。 

      现在我们假设一种最简单的情况,这个双向链表中只有两个节点,并且p1的prev和p2的next都指向空。但注意,这时p1本身管理一段空间,p2的prev也管理p1管理的这块空间,所以p1下的引用计数为2,在p1的析构函数时对其引用计数减一,发现并没有为0,所以选择不释放p1的空间和p1的引用计数的空间。这样就造成了内存泄漏。我们称之为:循环引用

    所以,弱指针weak_ptr就应运而生了,其实在库中shared_ptr和weak_ptr这两个智能指针类都公有继承了一个抽象的引用计数的类,所以,shared_ptr和weak_ptr的实现方式所差无几,就是二者的引用计数有区别。

    (4)weak_ptr

    weak_ptr也维护了一个引用计数,跟shared_ptr维护的引用计数或互不干扰,或相互协同。weak_ptr的指针会在weak_ptr维护的引用计数上加一,而shared_ptr会在shared_ptr维护的引用计数上加一,这样在循环引用时,就会因为对不同引用的判断的不同,使最终决定是否释放空间的结果也不相同。具体方式在下面举例说明。 

    结构体中使用两个弱指针weak_ptr管理它的next和prev域,而这个节点本身为shared_ptr。这种用法使得作用域完毕执行析构函数时按如下方式执行: 

    这样析构的顺序将变成如下过程,可避免循环引用

    p1(usecount=1,weakcount=2),p2(usecount=1,weakcount=2)——开始进入析构流程——首先析构p2指针本身,p1(usecount=1,weakcount=1),p2(usecount=0,weakcount=1)——然后只能开始析构p1指针本身,p1(usecount=0,weakcount=1),p2(usecount=0,weakcount=0)——可以释放p2的引用空间了,p1(usecount=0,weakcount=0),p2(usecount=0,weakcount=0)——最后p1的引用空间也可以被释放。

  • 相关阅读:
    css选择器中:first-child与:first-of-type的区别
    Chrome 快捷键
    notepad++ html格式化
    Linux VFS的主要的数据结构
    Linux根文件系统介绍
    Linux文件系统测试工具
    p​o​s​t​m​a​r​k​使​用
    虚拟文件系统
    linux文件系统初始化过程(6)---执行init程序
    linux文件系统初始化过程(4)---加载initrd(中)
  • 原文地址:https://www.cnblogs.com/mini-coconut/p/9391274.html
Copyright © 2011-2022 走看看