C++引用計數(reference counting)技術簡介(3)
1.將Reference Counting加到既有的Class
要想將引用計數施加到現有的實值物件Widget上,按照前面討論的,都需要修改Winget類的原始碼。但是,有時程式庫的內容不是我們呢可以修改的,又該如何做呢?
如果令Widget繼承自RCObject,我們必須增加一個RCWidget class給使用者使用,這很像之前關於String/StringValue的討論。RCWidget扮演String的角色,Widget扮演StringValue的角色。整個設計結構如下:
但這麼做的話就需要修改Widget使其繼承自RCObject。我們可以增加一個新的CountHolder class,用以持有引用計數,並令CountHolder繼承自RCObject。我們也令CountHolder內含一個指標,指向一個Widget。然後將smart RCPtr template以同樣聰明的RCIPtr template取代,RCIPtr template包含CountHolder的一個指標。RCIPtr 的”I”意指”indirect”間接。修改後的設計如下:
引用計數基類RCObject基本不變,其原始碼如下:
//引用計數基類
class RCObject{
public:
RCObject();//建構函式
RCObject(const RCObject& rhs);//拷貝建構函式
RCObject& operator=(const RCObject& rhs);//拷貝賦值運算子
virtual ~RCObject() = 0;//解構函式
void addReference();//增加引用計數
void removeReference();//減少引用計數,如果變為0,銷燬物件
void markUnshareable();//將可共享標誌設為false
bool isShareable() const;//判斷其值是否可共享
bool isShared() const;//判斷其值是否正在被共享
int getRefCount();//返回引用計數
private:
int refCount;//儲存引用計數
bool shareable;//儲存其值是否可共享的狀態
};
//建構函式,這裡refCount設為0,讓物件建立者自行或將refCoun設為1
RCObject::RCObject(void) :refCount(0 ), shareable(true){}
//拷貝建構函式,總是將refCount設為0,因為正在產生一個新物件,只被建立者引用
RCObject::RCObject(const RCObject&) : refCount(0), shareable(true){}
//拷貝賦值運算子,這裡只返回*this,因為左右兩方RCObject物件的外圍物件個數不受影響
RCObject& RCObject::operator=(const RCObject& rhs){
return *this;
}
//解構函式
RCObject::~RCObject(){}
//增加引用計數
void RCObject::addReference(){
++refCount;
}
//減少引用計數,如果變為0,銷燬物件
void RCObject::removeReference(){
if (--refCount == 0)
delete this;
}
//將追蹤其值是否可共享的成員設為false
void RCObject::markUnshareable(){
shareable = false;
}
//判斷其值是否可共享
bool RCObject::isShareable() const{
return shareable;
}
//判斷其值是否正在被共享
bool RCObject::isShared() const{
return refCount>1;
}
//返回引用計數
int RCObject::getRefCount(){
return refCount;
}
template RCIPtr的實現如下:
//智慧指標模板類,用來自動執行引用計數類成員的操控動作
template<typename T>
class RCIPtr{
public:
RCIPtr(T* realPtr = 0);//建構函式
RCIPtr(const RCIPtr& rhs);//拷貝建構函式
~RCIPtr();//解構函式
RCIPtr& operator=(const RCIPtr& rhs);//拷貝賦值運算子
const T* operator->() const;//過載->運算子
T* operator->();//過載->運算子
const T& operator*() const;//過載*運算子
T& operator*();//過載*運算子
private:
struct CountHolder :public RCObject{
~CountHolder() { delete pointee; }
T* pointee;
};
CountHolder* counter;
void init();//初始化操作
void makeCopy();//copy-on-write中的copy部分
};
//共同的初始化操作
template <typename T>
void RCIPtr<T>::init(){
if (counter->isShareable() == false){
T* oldValue = counter->pointee;
counter = new CountHolder;
counter->pointee = new T(*oldValue);
}
counter->addReference();
}
//建構函式
template <typename T>
RCIPtr<T>::RCIPtr(T* realPtr) :counter(new CountHolder){
counter->pointee = realPtr;
init();
}
//拷貝建構函式
template <typename T>
RCIPtr<T>::RCIPtr(const RCIPtr& rhs) :counter(rhs.counter){
init();
}
//解構函式
template <typename T>
RCIPtr<T>::~RCIPtr(){
counter->removeReference();
}
//拷貝賦值運算子
template <typename T>
RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs){
if (counter != rhs.counter){
counter->removeReference();
counter = rhs.counter;
init();
}
return *this;
}
//過載->運算子,const版本
template<typename T>
const T* RCIPtr<T>::operator->() const { return counter->pointee; }
//過載*運算子,non-const版本
template<typename T>
const T& RCIPtr<T>::operator*() const { return *(counter->pointee); }
//copy-on-write中的copy部分
template <typename T>
void RCIPtr<T>::makeCopy(){
if (counter->isShared()){
T* oldValue = counter->pointee;
counter->removeReference();
counter = new CountHolder;
counter->pointee = new T(*oldValue);
counter->addReference();
}
}
//過載->運算子,non-const版本
template <typename T>
T* RCIPtr<T>::operator->(){
makeCopy();
return counter->pointee;
}
//過載*運算子,non-const版本
template <typename T>
T& RCIPtr<T>::operator*(){
makeCopy();
return *(counter->pointee);
}
RCIPtr和RCPtr之間有兩個差異,一個是RCPtr物件直接指向實值,而RCIPtr物件通過中介層“CountHolder物件”指向實值。第二是RCIPtr將operator->和operator*過載了,如此一來只要有non-const access發生在被指物身上,copy-on-write(寫時複製)就會被執行。
有了RCIPtr,RCWidget的實現就很容易,因為RCWidget的每一個函式都只是通過底層的RCIPtr轉呼叫對應的Widget函式。Widget和RCWidget的示例程式碼如下。
class Widget{
public:
Widget(int s = 0) :size(s){}
Widget(const Widget& rhs) { size = rhs.size; }
~Widget(void) {}
Widget& operator=(const Widget& rhs){
if (this == &rhs)
return *this;
this->size = rhs.size;
return *this;
}
void doThis() { std::cout << "doThis()" << std::endl; }
int showThat() const {
std::cout << "showThat()" << std::endl;
return size;
}
private:
int size;
};
class RCWidget{
public:
RCWidget(int size = 0) :value(new Widget(size)){}
~RCWidget() {}
void doThis() { value->doThis(); }
int showThat() const { return value->showThat(); }
private:
RCIPtr<Widget> value;
};
注意,RCWidget沒有申明拷貝建構函式,也沒有賦值運算子和解構函式,就像先前的String class一樣,不再需要撰寫這些函數了,因為編譯器生成的預設版本做了正確的事情。
2.總結
引用計數的實現需要成本。每一個擁有計數能力的實值都有一個引用計數器,而大部分操作都需要能夠以某種方式檢查或處理這個引用計數器,因此物件的實值需要更多記憶體。而且引用計數的底層原始碼比沒有引用計數的複雜的多。
引用計數是個優化計數,其適用前提是物件常常共享實值。使用引用計數改善效率的時機有以下兩個:
第一,相對多數的物件共享相對少量的實值;
第二,物件實值的產生或銷燬成本很高,或是它們使用許多記憶體。