智能指針的實現機理

  • 介紹

  智能指針是用來實現指針指向的對象的共享的。其實現的基本思想:
  1.每次創建類的新對象時,初始化指針並將引用計數置爲1;
  2.當對象作爲另一對象的副本而創建時,拷貝構造函數拷貝指針並增加與之相應的引用計數;
  3.對一個對象進行賦值時,賦值操作符減少左操作數所指對象的引用計數(如果引用計數減至0,則刪除對象),並增加右操作數所指對象的引用計數;
  4.調用析構函數時,減少引用計數(如果引用計數減至0,則刪除基礎對象);
  4.重載“->”以及“*”操作符,使得智能指針有類似於普通指針的操作。
  根據以上分析,首先可以得出下面的類模板原型。

template <class T>
class SmartPointer
{
public:

    SmartPointer(T *p = 0);//構造函數
    SmartPointer(const SmartPointer& src);//拷貝構造函數
    SmartPointer& operator = (const SmartPointer& rhs);//賦值函數

    T* operator -> ();//重載->
    T& operator * ();//重載*
    ~SmartPointer();//析構函數

private:

    void MinusRef()
    {
        //被其他成員函數所調用
        if (--*m_pRef == 0)//自身的引用計數減1
        {
            //如果計數爲0,則釋放內存
            delete m_ptr;

            delete m_pRef;
        }

    }

    T *m_ptr;                           //保存對象指針
    size_t *m_pRef;                     //保存引用計數
};

  上面的私有成員函數MinusRef將引用計數減1。如果引用計數減至0,則刪除m_ptr所指對象。根據前面的分析,MinusRef只被賦值函數以及析構函數使用。
下面說明各個成員的具體定義。
  首先是構造函數與析構函數的定義。普通構造函數中,m_ptr與p指向同一塊內存,並初始化引用計數爲1。拷貝構造函數中與普通構造函數的不同之處爲引用計數需要加1。析構函數調用私有成員MinusRef對引用計數遞減,並且判斷是否需要釋放對象。代碼如下。

template<class T>
SmartPointer<T>::SmartPointer(T *p)     //普通構造函數
{
    m_ptr = p;                          //m_ptr與p指向同一內存
    m_pRef = new size_t(1);             //m_pRef初值爲1
}

template<class T>
SmartPointer<T>::SmartPointer(const SmartPointer<T>& src)//拷貝構造函數
{
    m_ptr = src.m_ptr;      //m_ptr與src.m_ptr指向同一內存
    m_pRef++;
    m_pRef = src.m_pRef;    //拷貝引用計數

}

template<class T>
SmartPointer<T>::~SmartPointer()       //析構函數
{
    MinusRef();             //引用減1,如果減後的引用爲0,則釋放內存
    std::cout<<"SmartPointer: Destructor"<<std::endl;
}

  接下來是“->”和“*”的重載。這兩個函數很簡單,只需要分別返回m_ptr以及m_ptr所指的內容即可。注意,如果m_ptr此時爲空,則應該拋出異常。代碼如下。


template<class T>
T* SmartPointer<T>::operator -> ()  //重載 ->
{
    if (m_ptr)
        return m_ptr;

    //m_ptr爲NULL時,拋出異常

    throw std::runtime_error("access through NULL pointer");
}
template<class T>
T& SmartPointer<T>::operator * () //重載 *
{
    if (m_ptr)
        return *m_ptr;
    //m_ptr爲NULL時,拋出異常

    throw std::runtime_error("dereference of NULL pointer");
}

  最後是賦值函數的實現:

template<class T>

SmartPointer<T>& SmartPointer<T>::operator = (const SmartPointer<T>& rhs)//賦值函數
{
    ++*rhs.m_pRef;      //rhs的引用加1
    MinusRef();         //自身指向的原指針的引用減1
    m_ptr = rhs.m_ptr;  //m_ptr合rhs.m_ptr指向同一個對象
    m_pRef = rhs.m_pRef; //複製引用
    return *this;
}

  這樣,就可以像 std::auto_ptr那樣來使用SmartPointer。以下先定義一個測試類,測試程序如下。

class Test
{
public:
    Test() {name = NULL;}
    Test(const char* strname)//構造函數
    {
        name = new char[strlen(strname)+1];//分配內存
        strcpy(name, strname);//拷貝字符串
    }
    Test& operator = (const char *namestr)//賦值函數
    {
        if (name != NULL)
        {
            delete name;//釋放原來的內存
        }
        name = new char[strlen(namestr)+1];//分配新內存
        strcpy(name, namestr);//拷貝字符串
        return *this;
    }
    void ShowName() {cout << name << endl;}
    ~Test()
    {
        if (name != NULL)
        {
            delete name;
        }
        name = NULL;
        cout << "delete name" << endl;
    }
public:
    char *name;
};

int _tmain(int argc, _TCHAR* argv[])
{
    SmartPointer<Test> t1;//空指針
    SmartPointer<Test> t2(new Test("fefd"));
    SmartPointer<Test> t3(new Test("wewew"));
    try
    {
        t1->ShowName();//空指針調用拋出異常
    }
    catch (const exception& err)
    {
        cout << err.what() << endl;
    }

    t2->ShowName();     //使用t2調用showName()
    *t2 = "David";      //使用t2給對象賦值
    t2->ShowName();     //使用t2調用showName()
    t2 = t3;            //賦值,原來t2的對象引用爲0,發生析構
                        //而t3的對象引用加1
    cout << "End of main..." << endl;

    getchar();
    return 0;
}

  main函數代碼第41行,t1指向一個NULL指針,因此調用ShowName時會出現異常(異常在重載的“->”函數中被拋出)。
  main函數代碼第48~50行,使用SmartPtr對象對Test對象進行操作,其方法與使用Test對象指針的操作方法相同。
  main函數代碼第51行,對t2進行賦值操作,操作完成後,t2引用的原對象發生析構(此對象沒有SmartPtr對象引用了),t2和t3引用同一個對象,於是這個對象的引用計數加1。注意,這裏我們並沒有顯示地對t2所引用的原對象進行釋放操作,這就是智能指針的精髓所在。

文章已遷移到個人網站 –>《智能指針的實現機理》

發佈了24 篇原創文章 · 獲贊 8 · 訪問量 11萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章