C++ 簡易string類實現(三)-抽離引用計數

C++ 簡易string類實現(二)-引用計數裏我們在String類實現里加入了引用計數,從而有效地實現了字符串的共享,降低內存消耗,提升了代碼的執行效率.引用計數(RC)不僅可以用於字符串,任何class如果其不同的對象可能擁有相同的值,都適用此技術.然而重寫class以便運用引用計數(RC),可能是一個大工程,我們中的大部分人該做的事情還有很多.如果我們能夠在一個與外界無任何關聯的環境下撰寫引用計數(RC)代碼(並測試以及說明),然後在必要的時機把它移植到classes身上,這樣可以有效地利用已有代碼去實現新功能,降低時間消耗和BUG數量.

如何去撰寫這樣的引用計數(RC)代碼?面向對象的封裝和繼承特性可以幫助我們有效地完成這個需求.

第一個步驟是,在C++ 簡易string類實現(二)-引用計數裏我們發現,StringValue內的ptr變量是和String相耦合的,但refCount和shareable和String沒有必要的關聯(換成其它class,對refCount和shareable的操作基本不變),因此我們將這兩個變量剝離出來,以此產生一個base class RCObject,作爲”reference-counted對象”之用.任何class如果希望自動擁有reference counting能力,都必須繼承這個class.RCObject將”引用計數器”本身以及增減計數值的函數封裝進來.此外,還包括一個函數,用來將不再被使用(也就是引用次數爲0)的對象銷燬掉.注:以上所需的成員函數,其實就是將C++ 簡易string類實現(二)-引用計數中和refCount及shareable變量相關的操作進行的封裝.

RCObject聲明如下:

//class String;
class RCObject
{
    //friend class String;
public:
    RCObject();

    /*RCObject(const RCObject& rhs_);*/

    /*RCObject& operator=(const RCObject& rhs_);*/

    //使RCObject成爲抽象基類,但該純虛函數需要提供
    //定義,不然會使被繼承的類無法在棧上創建(原因可
    //查閱如何僅在堆上或棧上分配內存)
    virtual ~RCObject() = 0;       

public:
    void addReference();

    void removeReference();

    void  markUnshareable();

    bool isShareable() const;

    bool isShared() const;

private:
    RCObject& operator=(const RCObject&) = delete;

private:
    int refCount;
    bool shareable;
};

RCObject定義如下:

RCObject::RCObject()
    :refCount(1), shareable(true)
{
    //std::cout << "RCObject" << std::endl;
}

/*RCObject::RCObject(const RCObject&)
    :refCount(1), shareable(true)
{
}*/

/*RCObject& RCObject::operator=(const RCObject&)
{
    return *this;
}*/

RCObject::~RCObject()
{
    //std::cout << "~RCObject" << std::endl;
}

void RCObject::addReference()
{
    ++refCount;
}

void RCObject::removeReference()
{
    if (--refCount == 0)
    {
        delete this;
    }
}

void RCObject::markUnshareable()
{
    shareable = false;
}

bool RCObject::isShareable() const
{
    return shareable;
}

bool RCObject::isShared() const
{
    return refCount > 1;
}

需要注意的地方:
(1)more effective C++中提供了RCObject(const RCObject& rhs_)拷貝構造函數,但我覺得這裏暫時沒有必要實現提供該函數,因爲,RCObject本身是虛基類,拷貝構造只有可能由派生類的構造函數主動調用RCObject的拷貝構造函數,否則派生類的構造函數只會調用RCObject的默認(無參)構造函數;
(2)默認(無參)構造函數裏,refCount應該初始化爲1,more effective C++將其初始化爲0,按照我的理解,是爲了迎合後面實現自動操作引用次數(Reference Count)而做的鋪墊,在這裏,我們不引入這個技術,所以將refCount初始化爲1(這裏初始化爲0,會造成未定義的錯誤);
(3)more effective C++裏對RCObject的賦值運算符聲明和定義如下:

RCObject& operator=(const RCObject& rhs_);

RCObject& RCObject::operator=(const RCObject&)
{
    return *this;
}

其對此的解釋是:”RCObeject涉及賦值動作,指向左右兩方RCObject外圍對象(例如本例的String對象)的個數都不會收到影響”,但如下圖,
這裏寫圖片描述RCObject的賦值操作,必然是因爲StringValue的賦值操作而引起的(RCObject是虛基類,沒有實例對象,無法直接調用其賦值運算符函數),此時sv1的ptr也指向sv2的ptr所指向的字符串S2,我們知道,RCOject記錄的不僅是當前有多少個String對象指向該ptr,更應該是一共有多少個String對象指向ptr所指向的字符串,如果僅僅是前者,effective C++裏的寫法固然沒有問題,但如果是後者的話(也必須是後者),那麼此時指向字符串S2的String對象就有5個,如果依舊是3個,那麼當s1,s2,s3都不擁有sv1時,sv1就會將其指向的資源析構,也就是說,儘管還有sv2的ptr指向字符串S2,但字符串S2還是被釋放了,這就會導致未定義行爲!!!
雖然,上述的情況一般不會發生,因爲在String裏,StringValue對象都是heap對象,String對象的賦值操作不會導致其StringValue指針變量發生賦值操作(除非手動進行指針賦值操作),所以完全不需要擔心上述問題,但該問題始終是潛在會發生的,解決的辦法:A.禁止StringValue的賦值操作(將StringValue的賦值運算符設置爲delete或者private);B.發生StringValue的賦值操作時,delete其ptr所指向的資源,並修改RCObject的實現,使得在發生賦值時,會正確修改refCount的值,代碼相對複雜,嘗試寫了下很多問題,所以僅僅在這裏提供一個上述的思路,若日後可以解決,再加上.這裏我們採用策略A,簡單而且有效.

StringValue的聲明,定義如下:

struct StringValue  : public RCObject
    {
        char* ptr;

        StringValue(const char* str_);

        //不可賦值操作
        StringValue& operator=(const StringValue&) = delete;

        ~StringValue();
    };

String::StringValue::StringValue(const char* str_)
{
    ptr = new char[strlen(str_) + 1];
    strcpy(ptr, str_);
}

String::StringValue::~StringValue()
{
    if (ptr)
    {
        delete[] ptr;
    }
}

全部代碼如下;

聲明:

class RCObject
{
public:
    RCObject();

    RCObject(const RCObject& rhs_);

    //使RCObject成爲抽象基類,但該純虛函數需要提供
    //定義,不然會使被繼承的類無法在棧上創建(原因可
    //查閱如何僅在堆上或棧上分配內存)
    virtual ~RCObject() = 0;       

public:
    void addReference();

    void removeReference();

    void  markUnshareable();

    bool isShareable() const;

    bool isShared() const;

private:
    RCObject& operator=(const RCObject&) = delete;

private:
    int refCount;
    bool shareable;
};

class String
{
public:
    String(const char* str_ = "");

    String(const String& str_);

    String& operator=(const String& str_);

    ~String();

public:
    const char& operator[](size_t index_) const;

    char& operator[](size_t index_);

private:
    struct StringValue  : public RCObject
    {
        char* ptr;

        StringValue(const char* str_);

        //不可賦值操作
        StringValue& operator=(const StringValue&) = delete;

        ~StringValue();
    };

    StringValue* _value;
};

實現:

RCObject::RCObject()
    :refCount(1), shareable(true)
{
    //std::cout << "RCObject" << std::endl;
}

RCObject::RCObject(const RCObject&)
//調用無參構造函數,注意:該調用僅能在
//初始化成員列表裏,如果在函數實現內調用,
//那麼僅僅是在棧上生成新的對象,而不是完成
//該對象的成員初始化
    :RCObject()
{
    std::cout << "RCObject" << std::endl;
}

RCObject::~RCObject()
{
    //std::cout << "~RCObject" << std::endl;
}

void RCObject::addReference()
{
    ++refCount;
}

void RCObject::removeReference()
{
    if (--refCount == 0)
    {
        delete this;
    }
}

void RCObject::markUnshareable()
{
    shareable = false;
}

bool RCObject::isShareable() const
{
    return shareable;
}

bool RCObject::isShared() const
{
    return refCount > 1;
}

String::String(const char* str_ /* = "" */)
    : _value(new StringValue(str_))
{
}

String::String(const String& str_)
{
    if (str_._value->isShareable())
    {
        _value = str_._value;
        _value->addReference();
    }
    else
    {
        _value = new StringValue(str_._value->ptr);
    }
}

String& String::operator=(const String& str_)
{
    /*
    //這樣寫存在問題,例如str2 = str3; str2 = str3;即重複一次,
    //如果是這種寫法,那麼會執行後續代碼一次,雖然結果是對的
    //但造成了不必要的運行消耗
    if (this != &str_)
    {
        return *this;
    }*/
    if (_value == str_._value)
    {
        return *this;
    }

    _value->removeReference();

    if (str_._value->isShareable())
    {
        _value = str_._value;
        _value->addReference();
    }
    else
    {
        _value = new StringValue(str_._value->ptr);
    }

    return *this;
}

String::~String()
{
    _value->removeReference();
}

const char& String::operator[](size_t index_) const
{
    //爲了簡化代碼,不引入_size變量記錄字符串長度
    if (index_ >= strlen(_value->ptr))
    {
        throw std::out_of_range("String out of range!");
    }
    return _value->ptr[index_];
}

char& String::operator[](size_t index_)
{
    if (index_ >= strlen(_value->ptr))
    {
        throw std::out_of_range("String out of range!");
    }

    //本對象和其他String對象共享同一個實值
    if (_value->isShared())
    {
        _value->removeReference();

        _value = new StringValue(_value->ptr);
    }

    _value->markUnshareable();
    return _value->ptr[index_];
}

String::StringValue::StringValue(const char* str_)
{
    ptr = new char[strlen(str_) + 1];
    strcpy(ptr, str_);
}

String::StringValue::~StringValue()
{
    if (ptr)
    {
        delete[] ptr;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章