C/C++编程:引用计数

1060 篇文章 292 订阅
本文探讨了如何通过引用计数技术来优化字符串类的实现,以达到多个对象共享相同值的目的。通过引入StringValue结构和RCObject基类,以及使用智能指针RCPtr,实现了对字符串值的共享和写时拷贝策略。这种方式提高了内存效率,同时确保了正确性,特别是在处理赋值和拷贝构造函数时。
摘要由CSDN通过智能技术生成

目的:允许多个相同值的对象共享这个值的实现

目标

class 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类是怎么实现的,但是通常的实现是每个string对象都有一个这个值的拷贝。比如,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;
};

这是:
在这里插入图片描述
但是我们想要的是:
在这里插入图片描述

实现引用计数

首先,我们需要一个地方来存储这个计数值。

  • 这个地方不能在String对象内部,因为需要的是每个String值一个引用计数值,而不是每个String对象一个引用计数值
  • 这意味着String值和引用计数间是一一对应的关系,所以我们将创建一个类来保存引用计数以及其跟踪的值。我们叫这个类为StringValue,又因为它唯一的用处是帮助我们实现String类,所以我们把它嵌套在String的所有区内。
  • 另外,为了便于String的所有成员函数读取其数据区,我们将StringValue声明为struct。需要知道的是:将一个struct内嵌在类的私有区内,能便于这个类的所有成员访问这个结构,但是阻止了其他任何人对它的访问(当然,除了友元)
class String{
public:
	...
private:
	struct StringValue{
		int refCount;
		char *data;
		StringValue(const char *initValue);
		~StringValue();
	};
	StringValue *value;
};

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

StringValue的主要目的是提供一个空间将一个特别的值和共享此值的对象的数目联系起来。其他都有String自己实现。

class String{
public:
	String(const char* initValue= "");
	String(const String& rhs);
	~String();

第一个构造函数被实现得尽可能的简单。我们用传入的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对象:

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

因此:

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

产生这样的数据结构:
在这里插入图片描述
对于析构函数:

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

写时拷贝

考虑一下数组下标操作([]),它允许字符串中的单个字符被读或写:

class String { 
public: 
	 const char&  operator[](int index) const; // for const Strings 
	 char& operator[](int index); // for non-const Strings 
	... 
};

这个函数的 const 版本的实现很容易,因为它是一个只读操作,String 对象的值不受影响:

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

非 const 的 operator[]版本就是一个完全不同的故事了。它可能是被调用了来读一个字符,也可能被调用了来写一个字符:

String s; 
... 
cout << s[3]; // this is a read 
s[5] = 'x'; // this is a write 

我们希望以不同的方式处理读和写。简单的读操作,可以用于const的operator[]类似的方式实现,而写操作必须用完全不同的方式来实现。

当我们修改一个String对象时,必须小心防止修改了其他与它共享的StringValue对象的其他String对象的值,不幸的是,C++编译器没法告诉我们一个特定的operator[]是用作读还是用作写的,所以我们必须保守的假设”所有“调用非const operator的行为都是为了写操作(Proxy类可以帮助我们区分读还是写)

为了安全的实现非const的operator[],我们必须确保没有其他String对象在共享这个可能的被修改的StringValue对象。简单的说,当我们返回StringValue对象的一个字符的引用时,必须确保这个StringValue的引用计数是1:

char& String::operator[](int index) 
{
	//如果我们与其他String对象共享一个值,
	//为我们自己分离一个单独的值副本
	 if (value->refCount > 1) { 
		 --value->refCount;  // 自减当前值的refCount,因为我们将不再使用该值
		 value = new StringValue(value->data); 	 //为我们自己创建一个值的副本
	 }
	return value->data[index];
}

这个”与其他对象共享一个值直到写操作时才拥有自己的拷贝“的想法在计算机科学中已经有很久的历史了,尤其在操作系统中:进行共享内存直到它们想在自己的页拷贝中修改数据位置。这个技巧如此常用,以至于有一个名字:写时拷贝。它是提高效率的一个更通用方法--Lazy 原则--的特例。

指针、引用与写时拷贝

大部分情况下,写时拷贝可以同时保证效率和正确性。只有一个挥之不去的问题。看一下这样的代码:

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

数据结构是这样的:
在这里插入图片描述
现在看增加一条语句:

String s2 = s1; 

String 的拷贝构造函数使得 s2 共享 s1 的 StringValue 对象,所以数据结构将是:
在这里插入图片描述
下面这样的语句将有不受欢迎的结果:

*p = 'x'; // modifies both s1 and s2!

String 的拷贝构造函数没有办法检测这样的问题,因为它不知道指向 s1 拥有的StringValue 对象的指针的存在。并且,这个问题不局限于指针:它同样存在于有人保存了一个 String 的非 const operator[]的返回值的引用的情况下。

至少有三种方法来应付这个问题。第一个是忽略它,假装它不存在。这是实现带引用计数的 String 类的类库中令人痛苦的常见问题。如果你有带引用计数的 String 类,试一下上面的例子,看你是否很痛苦。即使你不能确定你操作的是否是带引用计数的 String 类,也无论如何应该试一下这个例子。由于封装,你可能使用了一个这样的类型而不自知。

不是所以的实现都忽略这个问题。稍微好些的方法是明确说明它的存在。通常是将它写入文档,或多或少地说明“别这么做。如果你这么做了,结果为未定义。”无论你以哪种方式这么做了(有意地或无意地),并抱怨其结果时,他们辩解道:“好了,我们告诉过你别这么做的。”这样的实现通常很方便,但它们在可用性方面留下了太多的期望。

第三个方法是排除这个问题。它不难实现。但它将降低一个值共享与对象间的次数。它的本质是这样的:在每个StringValue对象中增加一个标准以指出它是否为可共享的。在最初(对象可共享时)标志打开,在非const的operator[]被调用时将它关闭。一旦标记被设为false,它将永远保持在这个状态:

class String { 
private: 
	 struct StringValue { 
		 int refCount; 
		 bool shareable; // add this 
		 char *data; 
		 StringValue(const char *initValue); 
		 ~StringValue(); 
	 }; 
... 
}; 
String::StringValue::StringValue(const char *initValue) : 
	refCount(1),  
	shareable(true) // add this 
{ 
	 data = new char[strlen(initValue) + 1]; 
	 strcpy(data, initValue); 
} 
String::StringValue::~StringValue() 
{ 
	 delete [] data; 
}

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

所有其它的成员函数也都必须以类似的方法检查这个共享标志。非 const 的 operator[]版本是唯一将共享标志设为 false 的地方:

char& String::operator[](int index) 
{ 
	 if (value->refCount > 1) { 
		 --value->refCount; 
		 value = new StringValue(value->data); 
	 } 
	 value->shareable = false; // add this 
	 return value->data[index]; 
}

如果使用proxy 类的技巧以区分读写操作,你通常可以降低必须被设为不可共享的 StringValue 对象的数目。

带引用计数的基类

引用计数不只用在字符串类上,只要是多个对象具有相同值的类都可以使用引用计数。改写一个类以获得引用计数需要大量的工作,而我们已经有太的工作需要做了。这样不好吗:如果我们将引用计数的代码写成与运行环境无关的,并能在需要时将它嫁接到其它类上?当然很好。很幸运,有一个方法可以实现它(至少完成了绝大部分必须的工作)。

第一步是构建一个基类RCObject,任何需要引用计数的类都必须从它继承。RCObject封装了引用计数功能,比如增加或者减少引用计数的函数。它还包含了一个字段以跟踪这个值对象是否可共享,并提供查找这个值和将它设为false的函数。不需要将可共享标志设为 true 的函数,因为所有的值对象默认都是可共享的。如上面说过的,一旦一个对象变成了不可共享,将没有办法使它再次成为可共享。

RCObject 的定义如下:

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::RCObject() : refCount(0), shareable(true) {} 
RCObject::RCObject(const RCObject&) : refCount(0), shareable(true) {}
RCObject& RCObject::operator=(const RCObject&) { 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]; 
	 strcpy(data, initValue); 
} 
String::StringValue::~StringValue() 
{ 
	 delete [] data; 
}

这个版本的 StringValue 和前面的几乎一样,唯一改变的就是 StringValue 的成员函数不再处理 refCount 字段。RCObject 现在接管了这个工作。

自动的引用计数处理

RCObject 类给了我们一个存储引用计数的地方,并提供了成员函数供我们操作引用计数,但调用这些函数的动作还必须被手工加入其它类中。仍然需要在 String 的拷贝构造函数和赋值运算函数中调用 StringValue 的 addReference 和 removeReference 函数。这很笨
拙。我们想将这些调用也移入一个可重用的类中,以使得 String 这样的类的作者不用再担心引用计数的任何细节。能实现吗?C++支持这样的重用吗? 当然可以

每个 String 对象包含一个指针指向 StringValue 对象:

class String { 
private:  
	 struct StringValue: public RCObject { ... }; 
	 StringValue *value; // value of this String 
	 ... 
}; 

我们必须操作StringValue对象的refCount字段,只要任何时候任一指向它的指针身上发生了任何有趣的事情。”有趣的事情“包括拷贝指针、给指针赋值和销毁指针。如果我们能够让指针自己检测这些事件并自动的执行对refCount字段的必须操作,那么我们就自由了。不幸的是,指针功能很弱,对任何事情作检测并作出反应都是不可能的。不过,有一个方法来增强它们:用行为类型指针的对象(智能指针)替代它们。

指针指针支持成员选择(->)和反引用(*)这两个操作符,就像真的指针一样,并和内建指针一样是强类型的:你不能将一个指向T的智能指针指向一个非T类型的对象。

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

//用于智能指针到t对象的模板类。T必须支持RCObject接口,通常是从RCObject继承的
template<typename T>
class RCPtr{
public:
	RCPtr(T * realPtr = 0);
	RCPtr(const RCPtr& rhs);
	~RCPtr();
	RCPtr& operator=(const RCPtr);
	T* operator->() const; 
 	T& operator*() const; 
private:
	T * pointee; 	 //这个对象正在模拟的哑指针
	void init();     // common 初始化
};

这个模板让灵巧指针对象控制在构造、赋值、析构时作什么操作。当这些事件发生时,这些对象可以自动地执行正确的操作来处理它们指向的对象的 refCount 字段。

例如,当一个 RCPtr 构建时,它指向的对象需要增加引用计数值。现在不需要程序员手工处理这些细节了,因为 RCPtr 的构造函数自己处理它。两个构造函数几乎相同,除了初始化列表上的不同,为了不写两遍,我们将它放入一个名为 init 的私有成员函数中供二者调用:

template<class T> 
RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr) 
{ 
	 init(); 
} 
template<class T> 
RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee) 
{ 
	 init(); 
} 
template<class T> 
void RCPtr<T>::init() 
{ 
	 if (pointee == 0) { // if the dumb pointer is 
		 return; // null, so is the smart one 
	 } 
	if (pointee->isShareable() == false) { // if the value 
 		pointee = new T(*pointee); // isn't shareable, 
 	} // copy it 
	pointee->addReference(); // note that there is now a  new reference to the value 
} 

将相同的代码移入诸如 init这样的一个独立函数是很值得效仿的,但它现在暗淡无光,因为在此处,这个函数的行为不正确。
问题是这个:当 init 需要创建 value 的一个新拷贝时(因为已存在的拷贝处于不可共享状态),它执行下面的代码:

pointee = new T(*pointee);

pointee的类型是指向T的指针,所以这个语句创建了一个新的T对象,并用拷贝构造函数进行了初始化。由于RCPtr是在String类内部,T将是String::StringValue,所以上面的语句调用了String::StringValue的拷贝构造函数。我们没有为这个类申明拷贝构造函数,所以编译器将为我们生成一个。这个生成的拷贝构造函数遵守 C++的自动生成拷贝构造函数的原则,只拷贝了 StringValue 的数据 pointer,而没有拷贝所指向的 char *字符串。这样的行为对几乎任何类(而不光是引用计数类)都是灾难,这就是为什么你应该养成为所有含有指针的类提供拷贝构造函数(和赋值运算)的习惯。

RCPtr< T>模板的正确行为取决于T含有正确的值拷贝(比如深拷贝)的拷贝构造函数。我们必须在String中增加一个这样的构造函数:

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的唯一假设。它还要求 T 从 RCObject 继承,或至少提供了 RCObject 的所提供的函数。事实上由于 RCPtr 对象只是被设计了指向引用计数对象的,这个假设并不过分。不过,这个假设必须被明确写入文档。

RCPtr< T>的最后一个假设是它所指向的对象类型为 T。这似乎是显然的。毕竟,pointee的类型被申明为 T*。但 pointee 可能实际上指向 T 的一个派生类。例如,如果我们有一个类 SpecialStringValue 是从 String::StringValue 继承的:

class String { 
private: 
	 struct StringValue: public RCObject { ... }; 
	 struct SpecialStringValue: public StringValue { ... }; 
	 ... 
}; 

我们可以生成一个 String,包容的 RCPtr指向一个 SpecialStringValue对象。这时,我们希望 init 的这句:

pointee = new T(*pointee);  // T is StringValue, but  pointee really points to a SpecialStringValue 

调用的是 SpecialStringValue 的拷贝构造函数,而不是 StringValue 的拷贝构造函数。我们可以提供使用虚拷贝构造函数来实现这一点。对于我们的 String 类,我们不期望从 StringValue 派生子类,所以我们忽略这个问题。

template<class T> 
RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs) 
{ 
	 if (pointee != rhs.pointee) { // skip assignments where the value  doesn't change 
	 if (pointee) { 
	 		pointee->removeReference(); // remove reference to current value 
	 } 
	 pointee = rhs.pointee; // point to new value 
	 init(); // if possible, share it else make own copy 
	 } 
	 return *this; 
}

析构函数很容易。当一个 RCPtr 被析构时,它只是简单地将它对引用计数对象的引用移除:

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

如果这个 RCPtr 是最后一个引用它的对象,这个对象将在 RCObject 的成员函数removeReference 中被析构。因此,RCPtr 对象无需关心销毁它们指向的值的问题。

RCPtr 的模拟指针的操作

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

合在一起

最后,我们将各个部分放在一起,构造一个基于可重用的 RCObject 和 RCPtr类的带引用计数的 String 类。

每个带引用计数的 Sting 对象被实现为这样的数据结构:
在这里插入图片描述
类的定义是:

template<class T> // template class for smart 
class RCPtr { // pointers-to-T objects; T 
public: // must inherit from RCObject 
	 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 for referencepublic: // counted objects 
	 void addReference(); 
	 void removeReference(); 
	 void markUnshareable();  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 to be used by
public: // application developers 
	 String(const char *value = ""); 
	 const char& operator[](int index) const; 
	 char& operator[](int index); 
private: 
	 // class representing string values 
	 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中的相同:消除构造函数中的重复代码。

这里有一个重大的不同:这个 String 类的公有接口和本条款开始处我们使用的版本不同。拷贝构造函数在哪里?赋值运算在哪里?析构函数在哪里?这儿明显有问题。

实际上,我们不再需要那些函数了。确实,String对象的拷贝仍然被支持,并且,这个拷贝将正确处理藏在后面的被引用计数的StringValue对象,但String类不需要写下哪怕一行代码让它发生。因为编译器为String自动生成的拷贝构造函数将自动调用器RCPtr成员的拷贝构造函数,而这个拷贝构造函数完成所有必须的对StringValue对象的操作,包括它的引用计数。

RCObject::RCObject() : refCount(0), shareable(true) {} 
RCObject::RCObject(const RCObject&) : refCount(0), shareable(true) {} 
RCObject& RCObject::operator=(const RCObject&) { 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; }
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) : pointee(realPtr)  { init(); } 
template<class T> 
RCPtr<T>::RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); } 
template<class T> 
RCPtr<T>::~RCPtr()  { if (pointee)pointee->removeReference(); } 
template<class T> 
RCPtr<T>& 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; }
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; }

在现存类上增加引用计数

到现在为止,我们所讨论的都假设我们能够访问有关类的源码。但如果我们想让一个位于支撑库中而无法修改的类获得引用计数的好处呢?不可能让它们从 RCObject 继承的,所以也不能对它们使用灵巧指针 RCPtr。我们运气不好吗?

不是的。只要对我们的设计作小小的修改,我们就可以将引用计数加到任意类型上。

首先考虑如果从 RCObject 继承的话,我们的设计看起来将是什么样子。在这种情况下,我们需要增加一个类 RCWidget 以供用户使用,而所有的事情都和 String/StringValue 的例子一样,RCWidget 和 String 相同,Widget 和 StringValue 相同。设计看起来是这样的:
在这里插入图片描述
我们现在可以应用这句格言:计算机科学中的绝大部分问题都可以通过增加一个中间层次来解决。我们增加一个新类 CountHolder 以处理引用计数,它从 RCObject 继承。我们让 CountHolder 包含一个指针指向 Widget。然后用等价的灵巧指针 RCIPter 模板替代 RCPtr模板,它知道 CountHolder 类的存在。(名字中的“i”表示间接“indirect”。)修改后的设计为:
在这里插入图片描述
如同 StringValue 一样,CountHolder 对用户而言,是 RCWidget 的实现细节。实际上,它是 RCIPtr 的实现细节,所以它嵌套在这个类中。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; // see below for an 
	 T* operator->(); // explanation of why 
	 const T& operator*() const; // these functions are 
	 T& operator*(); // declared this way 
private: 
	 struct CountHolder: public RCObject { 
		 ~CountHolder() { delete pointee; } 
		 T *pointee; 
	 }; 
	 CountHolder *counter; 
	 void init(); 
	 void makeCopy(); // see below 
}; 
template<class T> 
void RCIPtr<T>::init() 
{ 
	 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& rhs) : counter(rhs.counter) 
{ 
	init(); 
} 
template<class T> 
RCIPtr<T>::~RCIPtr() 
{ 
	counter->removeReference(); 
} 
template<class T> 
RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs) 
{ 
	 if (counter != rhs.counter) { 
		 counter->removeReference(); 
		 counter = rhs.counter; 
		 init(); 
	 } 
	 return *this; 
} 
template<class T> // implement the copy 
void RCIPtr<T>::makeCopy() // part of copy-on- 
{ // write (COW) 
	 if (counter->isShared()) { 
		 T *oldValue = counter->pointee; 
		 counter->removeReference(); 
		 counter = new CountHolder; 
		 counter->pointee = new T(*oldValue); 
		 counter->addReference(); 
	 } 
} 
template<class T> // const access; 
const T* RCIPtr<T>::operator->() const // no COW needed 
{ 
	return counter->pointee; 
} 

template<class T> // non-const 
T* RCIPtr<T>::operator->() // access; COW 
{ 
	makeCopy(); return counter->pointee;
} // needed 

template<class T> // const access; 
const T& RCIPtr<T>::operator*() const // no COW needed 
{ 
	return *(counter->pointee); 
}
 
template<class T> // non-const 
T& RCIPtr<T>::operator*() // access; do the 
{ 
	makeCopy(); 
	return *(counter->pointee); 
} // COW thing 

RCIPtr 与 RCPtr 只两处不同。第一,RCPtr 对象直接指向值对象,而 RCIptr 对象通过中间层的 CountHolder 对象指向值对象。第二,RCIPtr 重载了 operator->和 operator*,当有对被指向的对象的非 const 的操作时,写时拷贝自动被执行。

有了 RCIPtr,很容易实现 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() const; 
};

那么 RCWidget 将被定义为这样:

class RCWidget { 
public: 
	 RCWidget(int size): value(new Widget(size)) {} 
	 void doThis() { value->doThis(); } 
	 int showThat() const { return value->showThat(); } 
private: 
	 RCIPtr<Widget> value; 
};
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值