zoukankan      html  css  js  c++  java
  • 智能指针的使用与陷阱

    在包含指针的类中需要注意复制控制,复制指针时只复制指针中的地址,不会复制指针指向的对象。

     大多数c++类采用三种方法管理指针成员:

    1)指针成员采用常规指针型行为。

     2)采用智能指针

    3)采取值型行为

     常规指针缺陷:可能会出现悬垂指针。当一个指针复制到另一个指针,两个指针指向同一个对象,当一个指针删除对象时,另一个指针不知道,所以出现悬垂指针。即使使用默认合成复制构造函数也会出现,类本身无法避免。

     智能指针:加入了引用计数。引用计数跟踪该类有多少对象共享同一指针。当引用计数为0 时,删除对象。创建新类时,初始化指针并将引用计数置为1.进行复制时,增加相应引用计数值。赋值时,减少左操作数所指对象的引用计数的值(减至0,就删除对象),增加右操作数所指对象的引用计数的值。最后,调用析构函数时,减少引用计数的值。如果减至0,就删除对象。

    值型类:复制时会new一个新的副本,指针所指向的对象是唯一的,每个类对象独立管理。

    为了管理具有指针成员的类,必须定义三个复制控制成员:复制构造函数,赋值操作符和析构函数。这些成员可以定义指针成员的指针型行为或者值型行为。

    c++出现内存问题的地方一般:

    1)缓冲区溢出

    2)悬垂指针/野指针

    3)重复释放

    4)内存泄漏

    5)不配对的 new[]/delete

    都可以通过智能指针很好的解决这些问题,比如:

    1)->用vector/string或者自己写的buffer类管理,自动增加缓冲区大小,用成员函数操作,不直接通过野指针操作

     2),3),4)->可以通过智能指针解决,只在对象析构的时候释放一次内存,引用计数为0的时候才删除指针,自动释放

    5)->使用vector,自己释放内存

    智能指针的陷阱:

    这样的一个引用计数型智能指针目的是为了防止资源泄漏,但是只需要一个很小巧的代码就可以让这样的初衷化为乌有……

    class A
    {
    public:
        A() {cout<<"A CON"<<endl;}
        ~A() {cout<<"A DES"<<endl;}
       void hold(CountedPtr<A> ptr)
        {
           m_ptr = ptr;
        }
    private:
        CountedPtr<A> m_ptr;
    };
    
    void self_cir_area()
    {
        CountedPtr<A> pA(new A());
        pA->hold(pA);
    }

    可以看见,一个对象A中有一个引用计数型智能指针,这样的设计可能会很常见(指向自身类型的结构体——链表)

    但是,当自身循环引用发生的时候会怎么样呢? 下面就来看看这么两句代码

    CountedPtr<A> pA(new A());

     这里我们新建一个资源,并且把这个资源的管理权移交给pA这个引用计数型智能指针对象管理。如此,pA中的引用计数被初始化为1。

    pA->hold(pA);

     这里,我们把pA对象传入给实例化的A对象中的引用计数型智能指针m_ptr,m_ptr执行这样的一个成员函数:

    //assignment (unshare old and share new value)
    CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
               if (this != &p) {
                    dispose();
                    ptr = p.ptr;
                    count = p.count;
                    ++*count;
               }
               return *this;
    }

     因为这里很明显不是自身赋值,A中的m_ptr和pA不是同一个对象,所以进入if结构中调用下面的内容。dispose是用作清理,因为m_ptr并没有指向任何东西,所以第一个函数并没有真正的意义。

     然后

    m_ptr.ptr = pA.ptr;
    m_ptr.count = pA.count;
    ++(*m_ptr.count);  //(*pA.count)也被++

    到此,pA的引用计数为2

     嗯,下面就pA这个对象理所当然的离开了作用域,调用其析构函数:

    ~CountedPtr () throw() {
         dispose();
    }

    噢,是一个转向,调用其private成员函数dispose():

    很简单,将引用计数-1,由2变成1,不为0,所以if结构内的语句不被执行。

    由此,我们又制造了一个完美的太空垃圾……

    void dispose() {
          if (--*count == 0) {
               delete count;
               delete ptr;
          }
    }

    这样的循环引用问题应该是在设计的过程中就应该避免的,如果用UML语言描述

     A中持有一个 引用计数型智能指针 的语义就是 这个 持有关系 是需要在 A消失的时候所持有的对象也随之消失(这正是智能指针的作用,在脱离作用域自动清除其持有的资源)。如此就构成了 组合 关系。如果要表示 聚合 关系,即有 部分-整体 关系但是部分不随整体的消失而消失,这就不是 智能指针 所表达的语义。

    还有可能遇见的循环引用就是 A1 持有 A2, A2 持有 A1 的情况……

     这样A1,A2中对双方的引用计数都是2,当一方“销毁”的时候,双方的应用计数都变为1,实际上并没有销毁任何东西,制造了两个完美无暇的太空垃圾~

     这里又引发出一个问题,这样的资源泄漏问题实际上还是由程序员自身引起的。

     C++之所以是一个很容易出错的语言,很大一部分在于其资源的管理权力全权交给了程序员。这样的权力到底是造福了程序员还是迷惑了程序员呢?

  • 相关阅读:
    javascript定义类和实例化类
    c# 注册表操作,创建,删除,修改,判断节点是否存在
    asp.net 获得文件属性中的修改时间,获得系统文件属性的方法,最后一次写入时间
    研究“QQ开心农场”一点心得
    c# winform socket网络编程,点对点传输文件,socket文件传输,监听端口
    用C#创建Windows服务(Windows Services)
    c# webform js文件获取客户端控件,后台cs获取前台客户端控件的值,c#和js的交互
    c# asp.net webform web页面打印,可以控制需要打印和不需要打印的位置
    c# asp.net 调用系统设置字体文本框,设置label或页面字体,大小,FontDialog
    c# 计算程序执行时间,计算一段代码执行所用的时间,测试效率
  • 原文地址:https://www.cnblogs.com/shihuajie/p/5791729.html
Copyright © 2011-2022 走看看