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

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.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Python中文社区

Python迭代器使用详解

这一集的内容看起来比较绕,反反复复出现的是迭代二字。大家注意,这一节的内容很pythonic,是很有特色也非常重要的知识点。敲黑板啦!

1200
来自专栏小樱的经验随笔

【Java学习笔记之三十三】详解Java中try,catch,finally的用法及分析

这一篇我们将会介绍java中try,catch,finally的用法 以下先给出try,catch用法: try {   //需要被检测的异常代码 } ca...

3979
来自专栏博岩Java大讲堂

Java泛型的学习和使用

3524
来自专栏Coco的专栏

【优雅代码】深入浅出 妙用Javascript中apply、call、bind

902
来自专栏灯塔大数据

技术 | Python从零开始系列连载(十一)

导读 为了解答大家初学Python时遇到各种常见问题,小灯塔特地整理了一系列从零开始的入门到熟练的系列连载,每周五准时推出,欢迎大家学积极习转载~ 上一期学习了...

38710
来自专栏思考的代码世界

Python编程从入门到实践之列表|第1天

列表由一系列按特定顺序排列的元素组成。可以创建包含字母表中所有字母、数字0~9或 所有家庭成员姓名的列表;也可以将任何东西加入列表中,其中的元素之间可以没有任何...

3615
来自专栏Golang语言社区

Golang语言 - 以任意类型的slices作为输入参数

最近参与的一个业余项目,go-linq,让我了解到Go语言的类型系统并不是为任何类面向 对象编程而设计的。没有泛型,没有类型继承,也没有提供任何对这些特性有用的...

3878
来自专栏LanceToBigData

异常处理升级版

其实前面就写了一篇异常处理的文章,但是那个文章实在是感觉太详细了,不太好复习。所以今天我就再写一篇这样就更好复习了。 一、异常概述   在我们日常生活中,有时会...

2099
来自专栏Modeng的专栏

Javascript数组系列五之增删改和强大的 splice()

今天是我们介绍数组系列文章的第五篇,也是我们数组系列的最后一篇文章,只是数据系列的结束,所以大家不用担心,我们会持续的更新干货文章。

1382
来自专栏codingforever

经典算法巡礼(六) -- 排序之快速排序

快速排序正如她的名字,她是一种排序效率相当高的算法,而且可能是应用最广泛的排序算法了。快速排序流行的原因是她实现简单,适用于各种不同的输入数据且在一般应用中比其...

453

扫码关注云+社区

领取腾讯云代金券