[轉]C++中的指針--智能指針

Smart Pointer是C++中的一個大題目,要說清楚他的所有好處很需要費點力氣。我就一個功能一個功能的說。有我理解不透的地方希望大家指點。

1.copy-to-write
當生成一個C++ object的時候如果這個class很大,這個object會佔用很多空間。那麼每生成一個就佔用一片空間,這樣會佔用很多系統資源。同時降低效率。一個解決方法就是對用拷貝構造函數生成的object,讓他不存儲數據,而只存儲一個指向原來object數據的指針。 這樣空間就節省了很多。但問題在於這樣兩個object完全聯結在了一起。如果修改了其中一個,另一個也跟着變了。所以這種方法不可取。這裏講的 copy-to-write技術就是解決這類問題的方法。當通過引用一個已有object去拷貝構造新object時,新object只有一個指向已有 object的指針。這兩個object共享數據。直到其中一個需要修改數據的時候,再把這兩塊數據分離。這裏舉一個最簡化的例子。假設一個class叫 CLargeObject,裏面存有很多數據。我們用一個inner class來把所有數據放在一起,叫CData。CData裏面存有大量數據,例如一個數據庫。這裏用最簡單的模型來表示,假設只有一個整數int m_nVal; CData裏面需要包含另一個變量。叫作索引數目(reference count)。它記錄了指向這個CData object的來自CLargetObject類的指針各數。也就是說,總共有多少CLargeObject的object正在引用着當前的CData object。

class CLargeObject
{
private:
    struct CData
    {
    private:
        int m_nVal;
        int m_nReferenceCount;
    }
};

對於每個CLargeObject的object,我們用一個CData類的指針來指向其數據。
CData *m_pData;

CLargeObject至少有兩個構造函數。第一個是標準的構造函數,初始化其數據。這時數據是唯一的,所以必須新生成一個CData的object來存儲數據。
CLargeObject::CLargeObject(int nVal)
{
    m_pData = new Data(nVal);
}
而對於CData類的構造函數而言,初始化他的CLargeObject是第一個指向他的,這一時刻索引數目m_nReferenceCount是1。
CLargeObject::Data::Data(int nVal) : m_nVal(nVal), m_nReferenceCount(1) {}

CLargeObject的第二個構造函數是拷貝構造(copy constructor)。這樣生成的object不需要有新的數據,和已有的object共享數據就可以了。這是索引數目需要加1。表示又有一個object指向當前的CData了。
CLargeObject::CLargeObject(const CLargeObject &ob) // copy constructor
{
    ob.m_pData->m_nReferenceCount++;
    m_pData = ob.m_pData;
}


這樣CLargeObject就構造好了,使用了可能的最少的內存。下面看看他的析夠函數(destructor)。當一個object被delete的時候,它的數據不一定無效,如果別的object還在引用着這個數據,數據需要留下來。當然,數據的索引數目無論如何都要減1。
CLargeObject::~CLargeObject()
{
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
}

下面看一看賦值操作。先說用已有的CLargeObject賦值給這個CLargeObject。這時當前CLargeObject裏面的數據要指向已有的這個object,就搞定了。
CLargeObject& CLargeObject::operator = (const CLargeObject& ob)    // copy assignment
{
    ob.m_pData->m_nReferenceCount++;
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
    m_pData = ob.m_pData;

    return *this;
}

再來看看如何對CLargeObject裏面的數據進行真正的修改。這樣就一定需要對當前的object獨立操作了,否則就影響到了其它指向同一塊數據的CLargeObject。這樣CData類需要一個新的函數,生成只用於當前CLargetObject的數據。如果當前的引用數目是1,那麼當然這個CData就是隻用於這個CLargeObject的了。否則就重新new一個CData返回。
        Data* CLargeObject::CData::get_own_copy()    // clone if necessary
        {
            if (m_nReferenceCount==1)
                return this;
            m_nReferenceCount--;
            return new Data(m_nVal);
        }
CLargeObject修改前用這個函數得到唯一的object,然後對它賦值。
void CLargeObject::SetVal(int nNewVal)
{
    m_pData = m_pData->get_own_copy();
    m_pData->m_nVal = nNewVal;
}
對於所有可能改變CData值的操作,都需要用這種方法。

下面是隻讀函數,簡單。直接返回值,什麼特殊的都不用作。
int CLargeObject::GetVal() const
{
    return m_pData->m_nVal;
}


這樣copy-to-write技術就實現了。下面把完整的程序寫一下:
class CLargeObject
{
public:
    CLargeObject(int nVal);
    CLargeObject(const CLargeObject &ob);
    ~CLargeObject();

    CLargeObject& operator = (const CLargeObject& ob);
    void SetVal(int nNewVal);
    int GetVal() const;
private:
    struct Data
    {
    public:
        Data(int nVal) : m_nVal(nVal), m_nReferenceCount(1) {}
    private:
        friend class CLargeObject;
        Data* get_own_copy()    // clone if necessary
        {
            if (m_nReferenceCount==1)
                return this;
            m_nReferenceCount--;
            return new Data(m_nVal);
        }

        // control variables.
        int m_nReferenceCount;
    
        // actual data portion
        int m_nVal;
    };

    Data *m_pData;
};

CLargeObject::CLargeObject(int nVal)
{
    m_pData = new Data(nVal);
}

CLargeObject::CLargeObject(const CLargeObject &ob) // copy constructor
{
    ob.m_pData->m_nReferenceCount++;
    m_pData = ob.m_pData;
}

CLargeObject::~CLargeObject()
{
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
}

CLargeObject& CLargeObject::operator = (const CLargeObject& ob)    // copy assignment
{
    ob.m_pData->m_nReferenceCount++;
    if (--m_pData->m_nReferenceCount == 0)
        delete m_pData;
    m_pData = ob.m_pData;

    return *this;
}

void CLargeObject::SetVal(int nNewVal)
{
    m_pData = m_pData->get_own_copy();
    m_pData->m_nVal = nNewVal;
}

int CLargeObject::GetVal() const
{
    return m_pData->m_nVal;
}


很多存儲數據的系統class,如string,CString等都有這種設計。所以記住這個應用是很有必要的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章