引用计数这项技术,允许多个等值对象共享同一个实值。当对象运用了引用计数,它便拥有了自己,一旦不在有人使用它它便自动的销毁(听起来是不是很像智能指针,是的在std::tr1::share_ptr里的确用到了引用计数)。引用计数构建出了垃圾回收机制的一个简单形式。
产生一个引用计数的类并不难,但是要注意细节。产生数据共享的情况最有可能的就是这两种:拷贝构造、赋值运算符,所以要实现引用计数,首先要重载拷贝构造函数和复制运算操作符,假设这里我们以C++中最常用的String为例。我们需要的是为每一个字符串的值准备一个引用计数,而不是为每一个String对象准备一个引用计数,所以这暗示了对象的值和引用次数有一种耦合,而不是对象本身和引用次数有耦合关系。所以我用一个类(StringValue)来存放引用次数,也用它来存放对象的值。这样String类的定义就是以下这种形式:
class String
{
public:
String(const char *initValue="");
String(const String& argv);
String& operator=(const String& argv);
const char& operator[](int index) const;
char& operator [](int index);
~String();
private:
struct StringValue
{
int m_RefCount_;
char *m_pData_;
bool shareable;
StringValue(const char* initValue);
~StringValue();
};
StringValue *value;
};
String::StringValue::StringValue(const char *initValue)//默认构造函数负责初始化
:m_RefCount_(1)//讲引用计数设置成1
,shareable(true)
{
const int nLen = strlen(initValue)+1;
m_pData_ = new char[nLen];//分配字符串需要的空间,存储字符串
bzero(m_pData_,nLen);
strcpy(m_pData_,initValue);
}
String::StringValue::~StringValue()//析构函数负责销毁字符串
{
delete[]m_pData_;
m_pData_ = NULL;
}
String::String(const char *initValue)
:value(new StringValue(initValue))//默认构造函数,构建一个StringValue
{
}
String::String(const String &argv)//String的拷贝构造函数
{
if(argv.value->shareable)
{//默认情况下,只是增加引用计数的值,并没有重新分配新的空间
value = argv.value;
++value->m_RefCount_;
}
else
{
value = new StringValue(argv.value->m_pData_);
}
}
String &String::operator =(const String &argv)//String的赋值运算符
{
if(value == argv.value)
{
return *this;//如果是自己给自己赋值,什么也不做
}
if(--value->m_RefCount_ == 0)//将自己原来持有的字符串的引用计数减一
{
delete value;//判断如果原来的字符串已经没有对象在使用了,就销毁掉它,释放空间
}
value = argv.value;
++value->m_RefCount_;//将新的字符串的引用计数加一
return *this;
}
但是,String中有一个魔鬼,他会然一切都变得不和谐,那就是 s[0] = ‘a’这种形式,一旦String的值通过[]来改变,构造函数是没办法检测到的,唯一的办法就是,把它单独的独立出来进行处理。
const char &String::operator [](int index) const
{//读字符串中的某个元素
return value->m_pData_[index];//当知识单纯的读字符串而并没有改变它的值时,只需要单纯的返回
}
char &String::operator [](int index)
{//由于它并不知道是字符串的值有没有被改变,所以我们只能假设,他都改变了字符串的值
if(value->m_RefCount_ > 1)
{
--value->m_RefCount_;//重新开辟空间,另存起来,将之前字符串的引用计数减一
value = new StringValue(value->m_pData_);//这就是Copy-on-Write手法
}
value->shareable = false;
return value->m_pData_[index];
}
这里我们只是用String类为例,那么难道我们在其他的类要实现引用计数的时候都要事先添加一个类似StringValue的接口吗,如果一个类是从库文件中来的,你无法更改其源代码,那有该怎么办呢?其实我们只需要一个中间层,将引用计数的部分抽象出来是可以实现的。例如现在有一个类Widget,你无法修改其源代码,需要给它加上引用计数的手段该怎么办?
class Widget
{
public:
Widget(int size);
Widget(const Widget& argv);
~Widget();
Widget& operator=(const Widget& argv);
void doThis(void);
int showThat() const;
};
首先我们将引用计数相关的部分抽象出来,变成一个抽象类:
class RCObject
{
public:
void AddReference(void);//引用计数值加一
void RemoveReference(void);//引用计数值减一
void MarkReference(void);//屏蔽共享
bool IsShared(void);
bool IsShareable(void);
protected:
RCObject();
virtual ~RCObject()=0;
RCObject(const RCObject& argv);
RCObject& operator=(const RCObject& argv);
private:
int m_RefCount;//引用计数的值
bool shareable;//是否可共享
};
RCObject::RCObject()
:m_RefCount(0)
,shareable(true)
{
}
RCObject::RCObject(const RCObject &argv)
:m_RefCount(0)
,shareable(true)
{
}
RCObject &RCObject::operator =(const RCObject &argv)
{
return *this;
}
void RCObject::AddReference()
{
++m_RefCount;
}
void RCObject::RemoveReference()
{
if(--m_RefCount == 0)//不仅要负责将引用计数减一还要负责销毁对象
{
delete this;
}
}
void RCObject::MarkReference()
{
shareable = false;
}
现在我们需要一个类(如类A)来持有引用计数,类似于之前StringValue的作用,但是我们不希望每个持有类A的类都 要在拷贝构造和赋值运算的时候都去手动的调用AddReference和RemoveReference 我们最好让持有类A的类不需要关心任何细节,这听起来很扯,不过其实也不是不可能。我们可以用智能指针的思想来处理。
我们来实现一下这个类A:class RCIPtr
{
public:
RCIPtr(T *realPtr);
RCIPtr(const RCIPtr& argv);
~RCIPtr();
RCIPtr& operator=(const RCIPtr& argv);
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);
void MakeCopy(void);
};
这其实就是一个智能指针,所有持有它的类,在执行拷贝构造,或是赋值操作时,C++默认生成的拷贝构造和赋值操作会自动的调用RCIPtr的拷贝构造和赋值操作,所以持有RCIPtr的类不用去关心引用计数的细节。
template<class T>
void RCIPtr<T>::init(void)
{
if(counter->IsShareable() == false)
{
T *oldValue = counter->pointee;
counter = new CountHolder;
counter->pointee = new T(*oldValue);
}
counter->AddReference();//增加引用计数
}
template <class T>
RCIPtr<T>::RCIPtr(T *realPtr)
:counter(new CountHolder)
{
counter->pointee = realPtr;
init();
}
template <class T>
RCIPtr<T>::RCIPtr(const RCIPtr &argv)
:counter(argv.counter)
{
init();
}
template <class T>
RCIPtr<T>::~RCIPtr()
{
counter->RemoveReference();
}
template <class T>
RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& argv)
{
if(counter != argv.counter)
{
counter->RemoveReference();
counter = argv.counter;
init();
}
return *this;
}
我们要对Widget加上引用计数,因为我们无法直接更改Widget的代码,所以我们只能设计一个类,持有Widget并且加上引用计数,而且让该类的接口和Widget的接口一致。
class RCWidget
{
public:
RCWidget(int size);
void doThis(void);
void showThat(void);
private:
RCIPtr<Widget> value;
};
引用计数也是需要成本的,并不是什么地方用到它都能提高效率,如果在不合适的地方使用很有可能会造成更大的开销,这显然违背了我们的初衷。那么什么时候适合用引用计数呢?
1:相对多数的对象共享相对较少的实值的时候。
2:对象实值产生或销毁的成本很高或是它使用了很多的内存的时候