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

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

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

對(duì)于每個(gè)CLargeObject的object,我們用一個(gè)CData類的指針來指向其數(shù)據(jù)。
CData *m_pData;

CLargeObject至少有兩個(gè)構(gòu)造函數(shù)。第一個(gè)是標(biāo)準(zhǔn)的構(gòu)造函數(shù),初始化其數(shù)據(jù)。這時(shí)數(shù)據(jù)是唯一的,所以必須新生成一個(gè)CData的object來存儲(chǔ)數(shù)據(jù)。
CLargeObject::CLargeObject(int nVal)
{
    m_pData = new Data(nVal);
}
而對(duì)于CData類的構(gòu)造函數(shù)而言,初始化他的CLargeObject是第一個(gè)指向他的,這一時(shí)刻索引數(shù)目m_nReferenceCount是1。
CLargeObject::Data::Data(int nVal) : m_nVal(nVal), m_nReferenceCount(1) {}

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


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

下面看一看賦值操作。先說用已有的CLargeObject賦值給這個(gè)CLargeObject。這時(shí)當(dāng)前CLargeObject里面的數(shù)據(jù)要指向已有的這個(gè)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;
}

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

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


這樣copy-to-write技術(shù)就實(shí)現(xiàn)了。下面把完整的程序?qū)懸幌拢?br>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;
}


很多存儲(chǔ)數(shù)據(jù)的系統(tǒng)class,如string,CString等都有這種設(shè)計(jì)。所以記住這個(gè)應(yīng)用是很有必要的。