您的位置:首页 > 编程语言 > C语言/C++

C++引用计数(reference counting)技术简介(3)

2016-02-02 19:12 495 查看

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.总结

引用计数的实现需要成本。每一个拥有计数能力的实值都有一个引用计数器,而大部分操作都需要能够以某种方式检查或处理这个引用计数器,因此对象的实值需要更多内存。而且引用计数的底层源代码比没有引用计数的复杂的多。

引用计数是个优化计数,其适用前提是对象常常共享实值。使用引用计数改善效率的时机有以下两个:

第一,相对多数的对象共享相对少量的实值;

第二,对象实值的产生或销毁成本很高,或是它们使用许多内存。

参考文献

[1]More Effective C++.Scott Meyers著,侯捷译.P183-213.

[2]http://blog.csdn.net/ruan875417/article/details/48267527.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: