引用计数
本文的核心思想来源于:http://blog.youkuaiyun.com/armman/article/details/1714911
一 基本概念
在程序中为了防止对象的拷贝,经常使用的一个计数就是“引用计数”。例如:
String a, b, c, d, e;
a = b = c = d = e = "Hello";
如果所有的string对象只是共享一个“Hello”, 那么每个对象都包含该数据会造成内存的浪费,同时在对象拷贝时也会有不小的开销。那么我们可以用基本的引用计数来解决。
在C++ standard library中有个例子,我们贴一下完整的代码;
#ifndef COUNTED_PTR_HPP
#define COUNTED_PTR_HPP
/*class for counted reference semantics
*-deletes the object to which it refers when the last CountedPtr
* that refers to it is destroyed
*/
template <class T>
class CountedPtr {
private:
T* ptr; // pointer to the value
long* count; // shared number of owners
public:
//initialize pointer with existing pointer
//-requires that the pointer p is a return value of new
explicit CountedPtr (T* p=0)
: ptr(p), count(new long(1)) {
}
//copy pointer (one more owner)
CountedPtr (const CountedPtr<T>& p) throw()
: ptr(p.ptr), count(p.count) {
++*count;
}
//destructor (delete value if this was the last owner)
~CountedPtr () throw() {
dispose();
}
//assignment (unshare old and share new value)
CountedPtr<T>& operator= (const CountedPtr<T>& p) throw() {
if (this != &p) {
dispose();
ptr = p.ptr;
count = p.count;
++*count;
}
return *this;
}
private:
void dispose() {
if (--*count == 0) {
delete count;
delete ptr;
}
}
};
#endif /*COUNTED_PTR_HPP*/
分析代码可知,引用计数主要使用一下两个技巧:
(1) 由包含对象到包含指针。
template <class T>
class CountedPtr {
private:
T* ptr; // pointer to the value
long* count; // shared number of owners
}
这里, 对象包含的不再是T, 而是T的指针。将一个对象的最占用内存的地方放置到外面,然后自己包含指向它的指针。
(2) 包含一个指向引用计数对象(往往是一个int整数)的指针。
在上面的例子中, count即为引用对象, 包含一个引用对象的指针, 使得对象T的引用计数值对所有CountedPtr对象可见和一致。
一方面,可以把CountedPtr的接口设计成一个指针, 也可以设计成一个带引用计数的T对象。但是不管什么变种, 其核心都是以上两点。
实现图如下:
二: 写时拷贝
引用计数中有一个写时拷贝的技术,类似于os中的Copy-On-Write。例如:
String a = "abcdef";
a[1] = 'g';
我们可以在非const的[ ]操作时重新生成一份对象的拷贝,从而避免写操作的影响。但是该技术会碰到一个问题:
String s1 = "Hello";
char *p = &s1[1];
现在看增加一条语句:
String s2 = s1;
下面这样的语句将有不受欢迎的结果:
*p = 'x'; // modifies both s1 and s2!
那么如何解决上面的问题呢? 其实很简单,可以增加一个标志以指出它是否为可共享的。在最初(对象可共享时)将标志打开,在非const的operator[]被调用时将它关闭。一旦标志被设为false,它将永远保持在这个状态。
三 加入引用计数的代码架构
考虑设计一个支持引用计数的string类。为了让代码更具有可扩展性,我们可以增加一个引用计数的基类。类似如下:
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;
};
注意, 继承基类的子类在使用时,一定要用new 来创建,这样才符合上面提到的第二点。
那么我们可以如下设计string类。
通过继承, 引用计数变量存放在了单一的对象中。但是,该设计存在一些问题, 即每个String类都必须自己修改拷贝构造函数, 赋值函数等,有没有什么办法能自动化完成这些呢? 我们可以使用智能指针来帮我们完成这一切!新的设计如下:
上述设计使得String类自动的支持了引用计数,而不用添加额外的代码,非常的精彩。这里的StringValue类似于CountedPtr,而smart pointer使得String类的拷贝和赋值等操作不用关心引用计数。从而使代码更加简洁和组件化。
引用计数技术的实现类似于STL, 我们也可以使用三大组件。即:
智能指针, StringValue 和 RCObject。