程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 引用計數的智能指針的實現與思考

引用計數的智能指針的實現與思考

編輯:C++入門知識

引用計數在軟件開發中是一項非常重用的技術,它可以說是無處不,我們在不知不覺中都在和它打交道,比如 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& ref) const
    {
        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& ref) const
    {
        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。

總之,如果我們要在正式項目中使用這種方式的智能指針,使用者要對它的內部機制有深入的理解,同時建議不要同時混用智能指針和原始指針,另外建議只在模塊內部使用,而不要跨模塊傳遞智能指針。

上面是我對引用計數智能指針的一些理解和看法,如果有不正確的地方,歡迎指正。

 


摘自 厚積薄發
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved