在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;
}
}