您的位置:首页 > 其它

一种基于引用计数的智能指针的实现

2014-06-10 18:04 375 查看
介绍
什么是智能指针?答案很简单。智能指针就是一个智能的指针。什么意思呢?就是说,智能指针是这样一类对象:他们的行为像指针,但是他们干的活又比一般的指针多。智能指针既像普通的指针一样灵活,又能充分利用对象的优势(例如智能指针可以和对象一样,构造函数和析构函数自动被调用)。智能指针是用来处理普通指针引发的问题的(这也就是为什么称之为智能)
普通指针的缺陷
例如你有下面一段代码
char* pName = new char[1024];

SetName(pName);


if(null != pName)
{
      delete[] pName;
}
 
我们太多次的因为忘记删除pName,而引发bug。如果有人能够为我们释放不再有用的内存该多好呀(当然我们不考虑垃圾回收的机制)。如果指针自己能够处理释放的问题就好了。这就是智能指针要干的活。让我们写一个智能指针,来看看可以怎样更好的处理指针。
我们举一个实际一点的例子。假设我们有一个Person 类。
class Person
{
    intage;
   char* pName;
 
   public:
       Person(): pName(0),age(0)
       {
       }
       Person(char* pName, int age): pName(pName), age(age)
       {
       }
       ~Person()
       {
       }
 
       void Display()
       {
           printf("Name = %s Age = %d \n", pName, age);
       }
       void Shout()
       {
           printf("Ooooooooooooooooo",);
       }
};
下面是Person类客户端的代码 (客户端就是使用Person类的代码)
void main()
{
   Person* pPerson  = newPerson("Scott", 25);
   pPerson->Display();
   delete pPerson;
}
 
上面的代码,我们每new一个对象就要delete一次。我们想避免这样的麻烦。我们需要一些自动化的机制,析构函数闯进我的脑海。但是普通的指针没有析构函数。所以我们的智能指针需要有一个析构函数。我们将创建一个SP 类,SP 类将握着Person 类的指针,SP 类的析构函数被调用的时候要释放Person 类的内存。代码就可以变成下面的样子。
void main()
{
    SPp(new Person("Scott", 25));
   p->Display();
    //Dont need to delete Person pointer..
}
 
需要注意以下几点。
我们创建了SP 类的对象,这个对象握着Person 类的指针。当SP 对象所在的作用域结束的时候SP 对象将被析构,该对象负责释放Person 的指针,这样我们就不必自己手动释放了。

另外很重要的一点就是我们需要像使用Person 类的指针那样使用SP 类的对象。p->Display();p就好像是一个Person的指针。
 
智能指针的接口
既然智能指针应该表现的像普通指针一样,它就应该和普通指针有同样的对外接口。比如他需要支持一下两个操作符:
Dereferencing (operator *)
Indirection (operator ->)
我们来实现SP 类
class SP
{
private:
   Person*    pData; // pointer toperson class
public:
   SP(Person* pValue) : pData(pValue)
    {
    }
   ~SP()
    {
       // pointer no longer requried
       delete pData;
    }
 
   Person& operator* ()
    {
       return *pData;
    }
 
   Person* operator-> ()
   {   
       return pData;
    }
};
让我们的智能指针类更通用一些
使用模板修改我们的代码:
template < typename T > class SP
{
    private:
   T*    pData; // Generic pointer tobe stored
   public:
   SP(T* pValue) : pData(pValue)
    {
    }
   ~SP()
    {
       delete pData;
    }
 
   T& operator* ()
    {
       return *pData;
    }
 
    T*operator-> ()
    {
       return pData;
    }
};
 
void main()
{
   SP<PERSON> p(new Person("Scott", 25));
   p->Display();
    //Dont need to delete Person pointer..
}
好了,通用的智能指针类写好了,但是他真的智能吗?看下面的测试代码:
void main()
{
   SP<PERSON> p(new Person("Scott", 25));
   p->Display();
    {
       SP<PERSON> q = p;
       q->Display();
       // Destructor of Q will be called here..
    }
   p->Display();
}
看看这里发生了什么?p 和 q 指向同一个Person对象。当q的作用域结束,q执行析构函数,会释放Person对象。之后我们就不能调用 p->Display();了。应为p现在指向的是一个悬空的指针,就是一个野指针,调用将失败。直到没有任何人指向Person对象的时候我们才应该释放Person
对象。该怎么办呢?实现一个引用计数机制可以帮我们解决这个问题。
引用计数
我们写一个新的类RC,这个类将维护一个代表引用次数的整数。
class RC
{
   private:
    intcount; // Reference count
 
   public:
   void AddRef()
    {
       // Increment the reference count
       count++;
    }
 
    intRelease()
    {
       // Decrement the reference count and
       // return the reference count.
       return --count;
    }
};
现在我们有了一个引用计数类,我们将把RC和SP联系起来。我们的SP类中将维护一个RC对象的指针。不同的SP对象只要指向的是同一个普通对象,这些SP对象就以共享一个RC对象。为此,我们需要重写SP类的 赋值构造函数 和 拷贝构造函数。
template < typename T > class SP
{
private:
    T*   pData;       // 普通指针
    RC*reference; // 引用计数
 
public:
   SP() : pData(0), reference(0)  // 空 构造函数
    {
       // new 一个引用计数类
       reference = new RC();
       // 引用计数 + 1
       reference->AddRef();
    }
 
   SP(T* pValue) : pData(pValue), reference(0) // 带参数的构造函数
    {
       // new 一个引用计数类
       reference = new RC();
       // 引用计数 + 1
       reference->AddRef();
    }
 
   SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
    {
       // 拷贝构造函数,拷贝 普通指针 和 引用计数,注意都只是拷贝他们的指针。
       reference->AddRef();
    }
 
   ~SP()
    {
       // 析构
       if(reference->Release() == 0)
       {
           delete pData;
           delete reference;
       }
    }
 
   T& operator* ()
    {
       return *pData;
    }
 
    T*operator-> ()
    {
       return pData;
    }
   
   SP<T>& operator = (const SP<T>& sp)
    {
       // 赋值构造函数
       if (this != &sp) // 自己给自己赋值的时候什么也不干
       {
           // 减少老的引用计数,如果减到了0 就释放之
           if(reference->Release() == 0)
           {
                delete pData;
                delete reference;
           }
 
           // 把 普通指针和RC类的指针拷贝过来,引用计数 + 1
           pData = sp.pData;
           reference = sp.reference;
           reference->AddRef();
       }
       return *this;
    }
};
看看我们客户端的代码:
void main()
{
   SP<PERSON> p(new Person("Scott", 25));
   p->Display();
    {
       SP<PERSON> q = p; // 拷贝构造函数
       q->Display();
 
       SP<PERSON> r;
       r = p; // 赋值操作符
       r->Display();
       // q r 将在此析构
    }
   p->Display();
    //p析构,并将Person类释放掉
}
引用
当使用异常的时候,智能指针就显得特别有用。例如我们有下面一段代码:
void MakeNoise()
{
   Person* p = new Person("Scott", 25);
   p->Shout();
   delete p;
}
看上去挺好,但是Shout 方法里面如果抛异常怎么办? delete p;永远不会被执行,内存泄露了。那我们来处理异常吧:
void MakeNoise()
{
   Person* p = new Person("Scott", 25);
    try
    {
       p->Shout();
    }
   catch(...)
    {
       delete p;
       throw;
    }
   delete p;
}
捕获异常用重新抛出去,是不是有点费力。如果我们new了很多指针,代码就会变得更加笨重。如果引入智能指针呢?
void MakeNoise()
{
   SP<Person> p(new Person("Scott", 25));
   p->Shout();
}
我们在这里使用了智能指针,我们不必捕获异常了。如果Shout函数抛出了异常,函数栈将会回退,期间会调用p的析构函数。我们的申请的空间得以成功释放。
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: