智能指针的使用与陷阱

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

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

原文地址:https://www.cnblogs.com/shihuajie/p/5791729.html