引用计数的智能指针的实现与思考

引用计数在软件开发中是一项非常重用的技术,它可以说是无处不,我们在不知不觉中都在和它打交道,比如 Windows上的COM和Handle, Mac上的ref句柄,脚本语言中的垃圾回收技术。

但是在C++标准库中,却没有内置支持引用计数的技术的支持,下面我们就尝试封装自己的基于引用计数的智能指针。

一般来说,计数方法的实现有2种, 内置和外置 内置指的是对象本身就有计数功能,也就是计数的值变量是对象的成员;外置则是指对象本身不需要支持计数功能,我们是在外部给它加上这个计数能力的。

首先我们来看内置的方法: 
封装一个计数功能的对象 CRefObject :
class CRefObject
{
public:
    CRefObject()
    {
        m_nRefCount = 0;
    }

     int GetRefCount()  const
    {
         return m_nRefCount;
    }

     int AddRefCount()
    {
         return ++m_nRefCount;
    }

     int SubRefCount()
    {
         return --m_nRefCount;
    }

     void ResetRefCount()
    {
        m_nRefCount = 0;
    }

private:
     int    m_nRefCount;
};

然后封装我们的智能智能 CRefPtr<T>
// T should inherit from CRefObject
template<typename T>
class CRefPtr
{
public:
    T*  operator->()  const
    {
         return m_pRawObj;
    }

    T&  operator()()  const
    {
        assert(m_pRawObj != NULL);
         return *m_pRawObj;
    }

    T&  operator*()  const
    {
        assert(m_pRawObj != NULL);
         return *m_pRawObj;
    }

    T* GetPtr()  const
    {
         return m_pRawObj;
    }

     bool IsNull()  const
    {
         return m_pRawObj == NULL;
    }

     explicit CRefPtr(T* p = NULL)
    {
        m_pRawObj = p;
         if(p != NULL)
        {
            p->AddRefCount();
        }
    }

    CRefPtr( const CRefPtr&  ref)
    {
        m_pRawObj =  ref.m_pRawObj;
         if(m_pRawObj != NULL)
        {
            m_pRawObj->AddRefCount();
        }
    }

    ~CRefPtr()
    {
         if(m_pRawObj != NULL && m_pRawObj->SubRefCount() == 0)
        {
            delete m_pRawObj;
        }
    }

    CRefPtr&  operator = ( const CRefPtr&  ref)
    {
         if( this != & ref)
        {
             if(m_pRawObj != NULL
                && m_pRawObj->SubRefCount() == 0)
            {
                delete m_pRawObj;
            }

            m_pRawObj =  ref.m_pRawObj;

             if(m_pRawObj != NULL)
            {
                m_pRawObj->AddRefCount();
            }
        }

         return * this;
    }

     bool  operator == ( const CRefPtr&  refconst
    {
         return m_pRawObj ==  ref.m_pRawObj;
    }

private:
    T* m_pRawObj;
};

通过上面的代码可以看到,我们 要求要支持引用计数的对象都要从CRefObject继承,也就是给这个对象内置计数功能。

然后我们就可以这样使用了:
#include <iostream>
using namespace std;
#include "RefPtr.h"

class CTest: public CRefObject
{
public:
CTest(int n)
:m_n(n)
{
cout << "CTest(" << m_n << ") \n";
}
~CTest()
{
cout << "~CTest(" << m_n << ") \n";
}
void Print()
{
cout << m_n << "\n";
}
int m_n;
};
int main(int argc, char* argv[])
{
{
CRefPtr<CTest> p1(new CTest(1));
CRefPtr<CTest> p2(new CTest(2));
p1->Print();
p1 = p2;
}
system("pause");
return 0;
}

接下来我们尝试实现据通过外置方法实现引用计数的智能指针CRefIPtr<T>, 

代码如下:

template<typename T>
class CRefIPtr
{
public:
    T*  operator->()  const
    {
         return GetObjectPtr();
    }

    T&  operator()()  const
    {
         return GetObject();    
    }

    T&  operator*()  const
    {
         return GetObject();
    }

    T* GetPtr()  const
    {
         return GetObjectPtr();
    }

     bool IsNull()  const
    {
         return (m_pHolder != NULL 
            && m_pHolder->m_pRawObj != NULL);
    }

     explicit CRefIPtr(T* p = NULL)
    {
        m_pHolder =  new CRefHolder;

         if(m_pHolder != NULL)
        {
            m_pHolder->m_pRawObj = p;
            m_pHolder->AddRefCount();
        }
    }

    CRefIPtr( const CRefIPtr&  ref)
    {
        m_pHolder =  ref.m_pHolder;
         if(m_pHolder != NULL)
        {
            m_pHolder->AddRefCount();
        }
    }

    ~CRefIPtr()
    {
         if(m_pHolder != NULL
            && m_pHolder->SubRefCount() == 0)
        {
            delete m_pHolder;
        }
    }

    CRefIPtr&  operator = ( const CRefIPtr&  ref)
    {
         if( this != & ref 
            && m_pHolder !=  ref.m_pHolder)
        {
             if(m_pHolder != NULL
                && m_pHolder->SubRefCount() == 0)
            {
                delete m_pHolder;
            }

            m_pHolder =  ref.m_pHolder;

             if(m_pHolder != NULL)
            {
                m_pHolder->AddRefCount();
            }
        }

         return * this;
    }

     bool  operator == ( const CRefIPtr&  refconst
    {
         return m_pHolder ==  ref.m_pHolder;
    }

protected:
    T& GetObject()  const
    {
        assert(m_pHolder != NULL
            && m_pHolder->m_pRawObj != NULL);

         return *(m_pHolder->m_pRawObj);
    }

    T* GetObjectPtr()  const
    {
         if(m_pHolder != NULL)
        {
             return m_pHolder->m_pRawObj;
        }
         else
        {
             return NULL;
        }
    }

     class CRefHolder:  public CRefObject
    {
     public:
        CRefHolder()
        {
            m_pRawObj = NULL;
        }

        ~CRefHolder()
        {
            delete m_pRawObj;
        }

        T* m_pRawObj;
    };

private:
    CRefHolder* m_pHolder;
};

可以看到在外置的方法中我们内部封装了一个具有计数功能的 CRefHolder, 通过它实现我们的计数功能, 具体用法和上面CRefPtr类似,只不过CRefIPtr不再强制要求对象从CRefObject继承。

下面我们来讨论这2种方法的优缺点:
(1)从性能上来说,肯定内置的高,因为它不用通过新建内部Holder对象。
(2) 从易用性上来说, 外置的更方便,因为它不强制要求对象从CRefObject继承。
(3) 从使用范围上说, 外置的更广阔, 因为外置的方法支持C++ 内置类型也很方便, 比如CRefIPtr<int> p(new int(1)), 内置的却做不到。


但是外置的比内置在使用不当的情况下,有时更容易出错 ,比如下面的代码:
int main( int argc,  char* argv[])
{
    {
        CRefPtr<CTest> p1( new CTest(1));

        CTest* pRaw = p1.GetPtr();

        CRefPtr<CTest> p2(pRaw);
    }

    system("pause");

     return 0;
}
CRefPtr运行正常,但是改成CRefIPtr时,却会Crash。
究其原因是在内置情况下我们可以知道原始对象内部的计数值,但是外置情况下就无能为力了。
当然上面的用法本身就是不规范的,就像你这样用:
int main( int argc,  char* argv[])
{
    {
        CTest t(1);

        CRefPtr<CTest> p2(&t);
    }

    system("pause");

     return 0;
}
上面代码,无论用内置还是外置,都会Crash。

当然,我们上面的2种引用计数智能指针在实现上都没有考虑多线程的情况, 多线程情况只要给CRefObject加锁就可以了。

基于引用计数智能指针还有一个致命的缺点就是循环引用 ,会造成对象没法自动释放,这种情况下需要我们在需要释放对象时手动将指针值设成NULL。

总之,如果我们要在正式项目中使用这种方式的智能指针,使用者要对它的内部机制有深入的理解,同时建议不要同时混用智能指针和原始指针,另外建议只在模块内部使用,而不要跨模块传递智能指针。

上面是我对引用计数智能指针的一些理解和看法,如果有不正确的地方,欢迎指正。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值