29 Reference counting(引用计数)

Reference counting这项技术有两个动机:第一,是为了简化heap objects周边的簿记工作,reference counting建构出垃圾回收机制的一个简单形式。第二,是为了实现一种常识。如果许多对象有相同的值,将那个值存储多次时间愚蠢的事情。

没用Reference counting,首先看下如何造成许多同值对象,下面是一种可能:

class String		//标准的string类型有可能运用本条款技术,
{					 // 但非绝对必要
public:
	String(const char* value = "");
	String& operator = (const String& rhs);
	...
private:
	char* data;
};

String a,b,c,d,e;
a = b = c = d = e = "Hello";

很显然a~e都有相同的值“Hello”,该值得呈现方式String class如何实现而定,通常每一个String对象都携带自己的一份数据,例如String的赋值操作符可能实现如如下:

String& operator = (const String& rhs)
{
	if(this == &rhs) return *this;
	delete []data;
	data = new char[strlen(rhs.data) + 1];
	strcpy(data,rhs.data);
	return *this;
}

上述实现,就如下图所示:

这样很浪费和累赘,理想的状态是如下:

 

这里只有一份“Hello”,所以所有的String对象共享一份数据。

实际上不可能达到这样的理想,因为我们必须追踪记录有多少个对象共享此值,由于需要某些信息来记录“目前共享一实值”的对象个数,我们理想图应该做一些修改,有一个reference count(引用计数器)引入:

 

 

扫描二维码关注公众号,回复: 3245240 查看本文章
  • Reference Counting(引用计数) 的实现

引用计数的空间不可以设计在String对象内,因为我们需要为每一个字符串值转呗一个引用计数,而不是每一个字符串对象准备。这暗示了“对象值”和“引用计数”之间的耦合关系,所以产生一个class,不但存储引用计数,也存储它们追中的对象值。此class命令为StringValue,由于其存在的目的就是协助实现String class,所以嵌套在String的private中,可以让所有的String member functions都能访问StringValue,所以将StringValue声明为struct,看起来如下:

class String
{
public:
	...
private:
	struct StringValue{...};	//持有一个引用计数和一个字符串值
	StringValue* value;
};

下面是是StringValue定义:

class String
{
private:
	struct StringValue
	{
		int refCount;
		char* data;
	};
	StringValue(const char* initValue);
	~StringValue();
	...
};

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

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

这里一切很清楚,这不足以实现带引用计数的String类。一则,没有实现拷贝函数和赋值运算;二则,没有提供对refCount的操作。别担心,少掉的功能将由String类来提供。StringValue的主要目的提供一个空间将一个特别的值和共享次值的对象的数目关联起来。

我们首先开始处理String的成员函数,首先是构造函数:

class String
{
public:
	String(const char* initValue);
	String(const String& rhs);
	...
};

第一个构造函数简单,传入char*创建一个新的StringValue对象,并将正在构造的String对象指向这个新生成的StringValue:

String::String(const char* initValue):
	value(new StringValue(initValue));
{
	
}

这样用户可以这样使用代码:

String s("More Effective C++");

生成的数据结构如下:

 String对象的独立构造的,有同样初始化值的对象并不共享数据,所以这样的代码:

String s1("More Effective C++");
String s2("More Effective C++");

产生这样的数据结构:

消除这样的副本是可能:通过让String(或StringValue)对象跟踪已存在的StringValue,并只在不同串时才创建新的对象。

String的拷贝构造函数很高效,新生成的String对象与被拷贝的对象共享相同的StringValue对象:

String::String(const String& rhs):
	value(rhs.value)
{
	++value->refCount;
}

 这样的代码:

String s1("More Effective C++");
String s2(s1);

产生这样的数据结构:

这肯定比通常的的string 高效,因为不需要为新生成的string值分配内存、释放内存以及拷贝内容到这个内存。我们只是拷贝了一个指针并增加一次引用计数。

String类的析构同样高效,只有引用计数不为0的时候,也就至少有一个String对象使用这个值,这个值不可以被销毁。只有当唯一的使用者被析构了(也就是引用计数在进入函数前为1时),String的析构函数才销毁StringValue:

class String
{
public:
	~String();
};
String::~String()
{
	if(--value->refCount == 0) delete value;
}

现在考虑赋值函数:

class String
{
public:
	...
	String& operator = (const String&& rhs);
};

当用户写下s1 = s2;这样的代码时候,其结果s1和s2指向相同的StringValue。对象的引用计数应该在赋值的时候增加,并且,s1原来指向的StringValue对象的引用计数应该减少,因为s1不再具有这个值了,如果s1是拥有原来的唯一对象,这个值应该销毁。代码中如下:

String& String::operator = (const String& rhs)
{
	if(value == rhs.value)
		return *this;
	
	if(--value->refCount == 0)
		delete value;
	
	value = rhs.value;
	++value->refCount;
	return *this;
}
  • Copy-On-Write(写时拷贝)

为了完整验证reference-counted字符串,让我们继续考虑方括号操作符([]),它允许字符串中的个别字符被读取或被写:

class String
{
public:
	const char& operator[](int index) const;	//针对const Strings
	char& operator[](int index);				//针对非const String
	...
};

此函数的const版本实现起来十分直接,只要是读动作,字符串不受影响:

const char& String::operator[](int index) const
{
	return value->data[index];
}

operator[]的non-const版本却不同,这个函数有可能用来读取一个字符串,也可能用来写一个字符串:

String s;
...
cout << s[3];	//读
s[5] = 'x';		//写

为了安全的实现出non-const operator[],我们必须确保没有其他任何“共享”同一个StringValue“的String对象因写动作而改变。简单的说,任何时候当我们返回一个reference,指向String的StringValue对象内的一个字符时,我们必须确保该StringValue对象引用计数为1:

char& String::operator[](int index)
{
	//如果本对象和其他String对象共享一值
	//就分割(复制)出另一副本共本对象使用
	if(value->refCount > 1)
		--value->refCount;
	
	//为自己做的副本
	value = new StringValue(value->data);
	//返回一个reference,代表我们这个“绝对不被共享”的
	//StringValue对象内的一个字符。
	return value->data[index];
}
  •  Pointers,References,以及Copy-On-Write

实现出copy-on-write,使我们几乎得意保留效率和正确性。但是有一个挥之不去的问题坤然我们,考虑以下代码:

String* s1 = "Hello";
char* p = &s1[1];

此时数据结构如下:

 

现在加上新的一行:

String s2 = s1;

String copy constructor会造成s2共享s1的StringValue,所以数据结构如下:

但是这暗示下面的句子会让人不愉快:

*p = 'x'; //同时修改s1和s2的值

String copy constructor没办法检测出这个问题,因为它没法知道目前存在一个指针,指向s1的StringValue对象。这问题不限于指针,如果有人将“String”的non-const operator[]返回值“的reference存储起来,也会发生这样的问题。

至少有三种方法处理此等问题:第一、忽略它,假装不存在;第二、对此做说明“不要那么做,如果你不听,后果自负”,这样的产品往往拥有良好的效率,但离实用性还有差距;第三、为每一个StringValue对象加上一个标志(flag)变量,用以指示可否被共享。

下面是StringValue的修改版,包含一个“可共享”标志:

class String
{
private:
	struct StringValue
	{
		int refCount;
		bool shareable;			//新增
		char* data;
	};
	StringValue(const char* initValue);
	~StringValue();
	...
};

String::StringValue::StringValue(const char* initValue)
		:refCount(1),shareable(true)//新增
{
	data = new char[strlen(initValue) + 1];
	strcpy(data,initValue);
}

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

以下是copy constructor的心行为:

String::String(const String& rhs)
{
	if(rhs.value->shareable)
	{
		value = rhs.value;
		++value->refCount;
	}
	else
	{
		value = new StringValue(rhs.value->data);
	}
}

所有其他的String member functions都应该检查shareable。Non-const operator[]是唯一一个将shareble置为false的:

char& String::operator[](int index)
{
	if(value->refCount > 1)
		--value->refCount;
	
	value = new StringValue(value->data);
	value->shareable = false;	//新增此行
	return value->data[index];
}
  • 一个Reference-Counting(引用计数)基类

Reference-counting可用于字符串以外的场合。任何class如果其不同的对象可能拥有相同的值,都适用此技术。然而重写class以便运用reference counting可能是个大工程。我们可以产生一个base class RCObject,作为“reference-counted对象”只用。任何class希望拥有reference counting能力,就继承这个类:

class RCObject
{
public:
	RCObject();
	RCObject(const RCObject& rhs);
	RCObject& operator = (const RCObject& rhs);
	virtual ~RCObject() = 0;
	void addReference();
	void removeReference();
	void markUnshareable();
	bool isShareable() const;
	bool isShared() const;
private:
	int refCount;
	bool shareable;
};

RCObject::RCObject()
	:refCount(0),shareable(true)
{}

RCObject::RCObject(const RCObject& rhs)
	:refCount(0),shareable(true)
{}

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

RCObject::~RCObject()
{}


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


然后令StringValue继承自RCObject:

class String
{
private:
	struct StringValue: public RCObject
	{
		char* data;
		StringValue(const char* initValue);
		~StringValue();
	};
	...
};

String::StringValue::StringValue(const char* initValue)
{
	data = new char[strlen(initValue) + 1];
	stpcpy(data,initValue);
}

String::StringValue::~StringValue()
{
	delete [] data;
}
  • 自动操作Reference Count(引用计数)

RCObject class给我们放置了一引用计数的空间,也给我们一些member functions用来操作引用计数,但是这些函数必须手动调用,我们希望能够把这些调用动作移到一个可复用的class内,这么一来让String之类的classes不必操心reference counting的任何细节。

每个String对象都内含一个指针,指向StringValue对象,后者用以表现String的值:


class String
{
private:
	StringValue: public RCObject{...};
	StringValue* value;
	...
};

我们必须操作StringValue对象的refCount字段,只要任何时候指向它的指针发生任何有趣的事情。这些“有趣的事情”包括拷贝指针、给指针赋值和销毁。我们能够让指针自己检测这些事情自动执行对refCount字段的必须操作,我们就自由了,我们可以用智能指针。

这里提供引用计数的对象使用的智能指针模板:

template<class T>
class RCPtr
{
public:
	RCPtr(T* realPtr = 0);
	RCPtr(const RCPtr& rhs);
	~RCPtr();
	RCPtr& operator = (const RCPtr& rhs);
	T* operator->() const;
	T& operator*() const;
	
private:
	T* pointee;
	
	void init();
};

实现如下:

template<T>
RCPtr<T>::RCPtr(T* realPtr = 0)
	:pointee(realPtr)
{
		init();
}

template<T>
RCPtr<T>::RCPtr(const RCPtr& rhs)
	pointee(rhs.pointee)
{
	init();
}

template<T>
void RCPtr<T>::init()
{
	if(pointee == NULL)  //if the dumb pointer is null
		return ;		
	
	if(pointee->isShareble() == false)
		pointee = new T(*pointee);
	
	pointee->addReference();
		
}

此时的init行为不正确,我们没为StringValue实现自己的深拷贝函数,pointee类型的指向T的指针,所以pointee = new T(*pointee)构造新的T对象,由于RCPtr是在String类内部,T将是String::StringValue,所以我们要为String::StringValue实现深拷贝:

class String
{
private:
	struct StringValue: public RCObject
	{
		StringValue(const StringValue& rhs);
		...
	};
};

String::StringValue::StringValue(const StringValue& rhs)
{
	data = new char[strlen(rhs.data) + 1];
	strcpy(data,rhs.data);
}

用这种方式实现了RCPtr的构造函数后,类的其他对象实现很轻快。我们已经在init函数中测试源对象是否可以共享:

template<class T>
class RCPtr<T>::operator = (const RCPtr& rhs)
{
	if(pointee != rhs.pointee)
	{
		if(pointee)
			pointee->removeReference();
		
		pointee = rhs.pointee;
		init();
	}
	return *this;
}


template<class T>
RCPtr<T>::~RCPtr()
{
	if(pointee)
		pointee->removeReference();
}

template<class T>
T* RCPtr<T>::operator->() const
{
	return pointee;
}

template<class T>
T& RCPtr<T>::operator*() const
{
	return *pointee;
}
  • 合在一起

每个带引用计数的String对象被实现为这样的结构:

以下是代码声明部分:

template<class T>				//template class,用来
class RCPtr						//产生smart pointers-to-Tobjects
{								//T必须继承自RCObject
public:
	RCPtr(T* realPtr = 0);
	RCPtr(const RCPtr& rhs);
	~RCPtr();
	RCPtr& operator = (const RCPtr& rhs);
	T* operator->() const;
	T& operator*() const;
	
private:
	T* pointee;
	void init();
};

class RCObject				//base class,用于reference-counted objects
{
public:
	void addReference();
	void removeReference();
	void markUnshanreable();
	bool isShareable() const;
	bool isShared() const;
	
protected:
	RCObject();
	RCObject(const RCObject& rhs);
	RCObject& operator = (const RCObject& rhs);
	virtual ~RCObject() = 0;
	
private:
	int refCount;
	bool shareable;
};

class String		//应用性class,这是应用程序开发人员基础的层面
{
public:
	String(const char* value = "");
	const char& operator[](int index) const;
	char& operator[](int index);
	
private:
	struct StringValue: public RCObject
	{
		char* data;
		StringValue(const char* initValue);
		StringValue(const StringValue& rhs);
		void init(const char* initValue);
		~StringValue();
	};
	RCPtr<StringValue> value;
};

 在String::StringValue身上多了一个init函数,它的功能和RCPtr的同名函数相同:只是将constructors内的重复代码集中起来而已。

下面是RCObject实现代码:

RCObject::RCObject()
	:refCount(0),shareable(true){}
	
RCObject::RCObject(const RCObject& rhs)
	:refCount(0),shareable(true){}
	
RCObject& RCObject::operator = (const RCObject& rhs)
{ return *this; }

RCObject::~RCObject(){}

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

void RCObject::markUnshanreable()
{ shareable = false; }
bool RCObject::isShareable() const
{
	return shareable;
}
bool RCObject::isShared() const
{
	return refCount > 1;
}

以下是RCPtr的实现代码:

template<class T>
void RCPtr<T>::init()
{
	if(pointee == 0) return;
	if(pointee->isShareable == false)
		pointee = new T(*pointee);
	pointee->addReference;
}

template<class T>	
RCPtr<T>::RCPtr(T* realPtr = 0)
	:pointee(realPtr)
{ 
	init();
}

template<class T>	
RCPtr<T>::RCPtr(const RCPtr& rhs)
	:pointee(realPtr)
{
	init(); 
}

template<class T>
RCPtr<T>::~RCPtr()
{
	if(pointee)
		pointee->removeReference();
}

template<class T>
RCPtr& RCPtr<T>::operator = (const RCPtr& rhs)
{
	if(pointee != rhs.pointee)
	{
		if(pointee)
			pointee->removeReference();
		pointee = rhs.pointee;
		init();
	}
	return *this;
}

template<class T>
T* RCPtr<T>::operator->() const
{ return pointee; }

template<class T>
T& RCPtr<T>::operator*() const
{ return *pointee; }

下面是String::StringValue的实现代码:

void String::StringValue::init(const char* initValue)
{
	data = new char[strlen(initValue) + 1];
	strcpy(data,initValue);
}

String::StringValue::StringValue(const char* initValue)
{
	init(initValue);
}
String::StringValue::StringValue(const StringValue& rhs)
{
	init(rhs.data);
}

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

最后,String的实现代码:

String::String(const char* value = "")
	value(new StringValue(initValue)){}
	
const char& String::operator[](int index) const
{
	return value->data[index];
}
char& String::operator[](int index)
{
	if(value->isShared())
	{
		value = new StringValue(value->data);
	}
	value->markUnshanreable();
	return value->data[index];
}
  • 将Reference Counting加到既有的Classes身上

如果我们将reference counting用于程序库中的一个名为Widget的class,程序库内容是不可以更变的,所以没办法让Widget继承自RCObject,也就无法对Widget使用smart RCPtrs。不过只需要稍微修改设计,我们就可以为任何类型加上reference counting能力。

首先,让我们考虑,如果令Widget继承自RCObject,设计看起来如何。这种情况必须加上RCWidget class给client使用,但每件事都极似String/StringValue例子:RCWidget扮演String角色,Widget扮演StringValue角色,整体设计看起来如下:

我们可以增加一层间接性,我们增加一个新的CountHolder class,用以持有引用次数,冰灵CountHolder继承自RCObject。我们领CountHolder class内含一个指针,指向Widget。如下图:

 

 就像“StringValue只能实现细节,不需要让String的用户知道”一样,CountHolder也是实现细节,不需要让RCWidget的用户知道。事实上它是RCIPtr的实现细节,所以把它嵌套放入RCIPtr class内部。RCIPtr实现如下:

template<class 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();
};


template<class T>
void RCIPtr<T>::init()
{
	if(counter->isShareable == false)
	{
		T* oldValue = counter->pointee;
		counter = new CountHolder;
		counter->pointee = new T(*pointee);
	}	
	pointee->addReference;
}

template<class T>
RCIPtr<T>::RCIPtr(T* realPtr)
	:counter(new CountHolder)
{
	counter->pointee = realPtr;
	init();
}

template<class T>
RCIPtr<T>::RCIPtr(const RCIPtr& rhs)
	:counter(rhs.counter)
{
	init();
}

template<class T>
RCIPtr<T>::~RCIPtr()
{
	counter->removeReference;
}

template<class T>
RCIPtr& RCIPtr<T>::operator = (const RCIPtr& rhs)
{
	if(counter != rhs.counter)
	{
		counter->removeReference();
		counter = rhs.counter;
		init();
	}
	return *this;
}

template<class T>
const T* RCIPtr<T>::operator->() const
{
	return counter->pointee;
}

template<class T>
const T& RCIPtr<T>::operator*() const
{
	return *(counter->pointee);
}

RCIPtr和RCPtr之间有两个差异。第一,“RCPtr对象”直接指向实值,而“RCIPtr对象”通过CountHolder指向实值。第二,RCIPtr将operator->和operator*重载了,如此一来只要有non-const access发生于被指物身上,copy-on-write就会自动执行。

有了RCPtr,RCWidget的实现就很容易,因为RCWidget的每一个函数只是通过底层的RCIPtr转调用对应的Widget河南省,如果Widget看起来像这样:

class Widget
{
public:
	Widget(int size);
	Widget(const Widget& rhs);
	~Widget();
	Widget& operator = (const Widget& rhs);
	void doThis();
	int showThat();
};

那么RCWidget,应该定义如下:

class RCWidet
{
public:
	RCWidet(int size):value(new Widget(size)){}
	void doThis(){value->doThis();}
	int showThat(){return value->showThat();}

private:
	RCIPtr<Widget> value;
};

reference counting最适合使用的时候:

  • 相对多数的对象共享相对少了的实值
  • 对象实值得产生和销毁成本很高,或是他们使用许多内存

 

猜你喜欢

转载自blog.csdn.net/weixin_28712713/article/details/82141589