实战智能指针(Smart Pointer)

(本站文章均出自原创,转载请注明出处~)


    最近在阅读Gamebryo的文档,先读的Object System这部分。Gamebryo中从NiRefObject继承下来的类都支持引用计数,也就是说支持0引用下对象自动销毁。
一直以来都听闻智能指针的大名,平常项目中并没有使用过,所以也一直没抽空去研究,今天花了点时间自己实现了一个智能指针。
    要实现一个智能指针就要先实现一个支持引用计数的类,主要负责增加计数,减少计数,销毁持有的对象指针。大致实现如下:

 

class RefObject
template<class T> class RefObject
{
public:
    
explicit RefObject(T* p):m_RefCount(1),m_pContainer(p)
    {
    }
    
~RefObject()
    {
        decRef();
    }
    
void incRef()
    {
        
++m_RefCount;
    }
    
void decRef()
    {
        
if(--m_RefCount == 0)
        {
            destroy();
        }
    }
    T
* getPtr()
    {
        
return m_pContainer;
    }
private:  
    
void destroy()
    {
        delete m_pContainer;
        m_pContainer 
= 0;
        m_RefCount 
= 0;
        delete 
this;
    }

private:
    T
*           m_pContainer;
    unsigned 
int m_RefCount;

private:
    RefObject(
const RefObject&);
    
const RefObject& operator= (const RefObject&);
};

 

该模板类将持有一个使用者指定的对象T类型的指针,并维护该指针被引用的数量。在destroy函数中销毁T类型指针和自己。为什么要删除自己呢?这是因为RefObject将和T共存亡。因为我希望用户可以将任意类放入智能指针内部,而不是从RefObject继承下来。所以RefObject是为T而生(由SmartPtr new出来的)。当用户直接使用RefObject时需要自己去调用incRef和decRef去增加和减少计数,因此即将设计的类SmartPtr实际上就是封装下RefObject,不需要用户来调这两个函数。SmartPtr实现大致如下: 
class SmartPtr
template<class T> class SmartPtr
{
public:
    SmartPtr():m_pRefObject(
0)
    {
    }
    
explicit SmartPtr(T* p)
    {
        m_pRefObject 
= new RefObject<T>(p);
    }
    SmartPtr(
const SmartPtr& other):m_pRefObject(other.m_pRefObject)
    {
        m_pRefObject
->incRef();
    }
    
~SmartPtr()
    {
        
if(m_pRefObject)
            m_pRefObject
->decRef();
    }
    
const SmartPtr<T>&  ff;">operator= (const SmartPtr& other)
    {
        
if(m_pRefObject != other.m_pRefObject)
        {
            m_pRefObject 
= other.m_pRefObject;
            m_pRefObject
->incRef();
        }
        
return *this;
    }
    T
* operator-> () const
    {
        
return m_pRefObject->getPtr();
    }
    T
& operator* () const
    {
        
return *m_pRefObject->getPtr();
    }
protected:
    RefObject
<T>* m_pRefObject;
};

 

 SmartPtr类在hold住一个T类指针时就new出一个RefObject与之相对应,重载赋值操作符来实现每赋值一次增加引用计数,析构函数中减少引用计数。并且重载->操作符以使SmartPtr使用起来像一个真正的指针。

    一切都非常简单,不是吗?当然这两个类实现的并不完整,有很多细小的功能没有提供,但大体脉络是抓住了。

 

    下面看看如何使用吧:

 

    int main()
    {
        {
            SmartPtr
<CSample> spSam(new CSample());
            spSame
->DoSomething();
        }
    }

 完全不用delete,实现垃圾自动回收效果,很酷吧。 哈哈,我初次写完这些代码是也觉的很爽,终于体验到了使用Java,C#的感觉了。可是智能指针还有一个致命的问题,这个问题就像是一盘美食中的苍蝇,让你心头非常不爽,并且对智能指针的好感立刻全无。哈哈哈,那就是:循环引用。当你写的类中不幸出现循环引用,那么你的智能指针将

不在智能,内存泄露随之产生,违背了智能指针的初衷。

 struct A

{
    SmartPtr
<B> b;
}
struct B
{
    SmartPtr
<A> a;
}

int main()
{
    SmartPtr
<A> aa(new A());
    SmartPtr
<B> bb(new B());

    aa.b 
= bb;
    bb.a 
= aa;
}

 

 

 如果你写了以上程序(虽然不常见,因为这种设计是非常糟糕的)你的aa和bb将无法释放!!!导致无法释放的原因是什么?这是因为aa获取了一次bb的所有权,但aa销毁时并没有释放这次获得的所有权,当把bb赋给aa的b时,bb的引用计数变为2,当bb销毁时bb所持有的对象引用减为1,而并不是0,所有他并不会释放他所持有的实际指针。其实

这种情况跟你写如下代码一样:

 class C

 

{
    A
* a;
    C()
    {
        a 
= new A();
    }
}

 

类C在构造函数里new了一个A,却并没有在析构函数里delete它!

 

    我的解决方法是设计一个新类:SmartPtrHolder,此类用来存放一个SmartPtr,但并不增加计数。一种比较时髦的解释叫:弱引用。实现大致如下:

 

template<class T> class SmartPtrHoder
{
public:
    SmartPtrHoder():m_pRefObject(
0)
    {
    }
    SmartPtrHoder(
const SmartPtr<T>& sp):m_pRefObject(sp.m_pRefObject)
    {
    }

    SmartPtrHoder
& operator= (const SmartPtr<T>& sp)
    {
        
if(m_pRefObject != sp.m_pRefObject)
        {
            m_pRefObject 
= sp.m_pRefObject;
        }

        
return *this;
    }
    SmartPtr
<T> use()
    {
        
return SmartPtr<T>(*this);
    }
protected:
    RefObject
<T>* m_pRefObject;
};

 

这次重载的赋值操作符中并不增加SmartPtr的引用计数。当你需要使用这个SmartPtr时需要调用use()函数来把SmartPtr从SmartPtrHolder中取出来。

另外需要在SmartPtr中增加一个构造函数以支持从SmartPtrHoder中构造出一个SmartPtr。代码如下:

    explicit SmartPtr(SmartPtrHoder<T>& spHolder):m_pRefObject(spHolder.m_pRefObject)
    {
        m_pRefObject
->incRef();
    }

 

 

这样再次设计A和B时: 

 

 struct A

 

{
    SmartPtrHolder
<B> b;
}
struct B
{
    SmartPtrHolder
<A> a;
}

 

 

 

  这样就不会出现循环引用了。此时我想起了一位大师的话,翻译成中文大致如下:你现在有一个问题,你会说:“嗯~ 我准备用正则表达式来解决这个问题”。现在你有两个问题!

    
  使用智能指针本身就是一个问题。所以还是谨慎为好!

 

posted @ 2010-05-19 21:49  Jee  阅读(469)  评论(0编辑  收藏  举报