C++实践(16):引用计数实现

 我们在做设计时,将接口与实现相分离是一个基本的策略。分离接口与实现主要有两种技术:
    (1)抽象基类。这个大家比较熟悉,在C++中是声明了纯虚函数的类,在Java、C#等语言中有现成的关键字。设计时将接口部分放在abstract base class中,它们是一个virtual析构函数和一组pure virtual函数。实现部分由各子类担当。这种类称为Interface Class,通过继承的方式来实现。
    (2)句柄类。即pimpl手法,把隶属对象的数据(即实现)从原对象中抽离出来,封装成一个称为impl的实现对象,在原对象中用一个指针成员指向它。pimpl即pointer to implementation,这种impl类称为Handle Class,它通过组合方式的来实现,主类的数据被抽离成一个独立的类(称为数据类,或值类),通过组合一个它的指针来完成工作。我们知道,在设计时应该优先使用组合而不继承,因为组合的耦合性更低。pimpl手法在C++的程序库设计中到处都是。
    用组合的方式来做设计时,如果多个主类对象的数据相同,我们经常可以让它们共享这一个值对象,以提高性能,这就需要对共享一个值对象的多个主对象进行引用计数。例如对字符串类String,它里面会有一个char* data的数据,我们可以把这个数据抽离出来设计成一个独立的值类StringValue,在String类中用一个StringValue*型指针指向数据。当进行拷贝或赋值时,比如s1="abcd",s2=s1,我们并不把s1的数据"abcd"深拷贝(对指针所指向的数据进行拷贝)给s2,而是让s2与s1共享这一份数据,这样可以大大提高性能。因此,我们需要对共享值对象"abcd"的主对象个数进行引用计数,要跟踪有多少个主对象引用了这一个值对象,引用计数一旦变成0就删除这个值对象。当我们需要写入时,比如s2[2]='x',这时就要执行真正的拷贝,把s1的"abcd"拷贝给s2,然后把s2的"abcd"修改为"abxd",这就是写时拷贝技术。从这可以看出,任何的组合方式都可以看作是一种pimpl手法,被组合的对象可以认为是主对象的值对象。智能指针类就是这样的,它所指向的对象可以认为是智能指针对象的数据。
    1、引用计数类。 在前面的“智能指针”介绍中我们实际上已经实现了引用计数,simplerefcount.hpp中的SimpleReferenceCount就是一个引用计数类,不过它是通过成员函数模板来对任意的指针类型进行计数,这实际上是相当于用模板的方式来实现引用计数的。现在我们需要对一般类型的对象进行引用计数,我们改用继承的方式来实现,把引用计数设计成抽象基类RCObject,值对象需要计数,它必须继承这个抽象基类,因此引用计数基类成为值对象的一部分

view plaincopy to clipboardprint?
//rcobject.hpp:引用计数类,是抽象类,因此只能作为基类使用,被计数的类要继承它  
#ifndef RCOBJECT_HPP  
#define RCOBJECT_HPP  
#include <cstddef>  
class RCObject{  
private:  
    std::size_t refCount; //计数变量  
    bool shareable; //被计数对象(即值对象)是否可以共享的标识,默认可以共享  
public:  
    RCObject():refCount(0),shareable(true){  
    }  
    RCObject(RCObject const& rhs) : refCount(0),shareable(true){  
    }  
    RCObject& operator=(RCObject const& rhs){  
        return *this;  
    }  
    virtual ~RCObject()=0; //作为基类使用,则析构函数一般要声明为vritual  
                                 //注意纯虚的虚构函数必须提供实现  
    void addReference(){  
        ++refCount;  
    }  
    void removeReference(){  
        if(--refCount==0)  
            delete this; //使用时继承RCObject的值对象(即被计数的对象)必须创建在堆上  
    }  
    void markUnshareable(){  
        shareable=false;  
    }  
    bool isShareable() const{ //值对象是否可共享  
        return shareable;  
    }  
    bool isShared() const{ //值对象是否已经被共享  
        return refCount>1;  
    }  
};  
RCObject::~RCObject(){ //纯虚的析构函数必须有定义  
}  
#endif 
//rcobject.hpp:引用计数类,是抽象类,因此只能作为基类使用,被计数的类要继承它
#ifndef RCOBJECT_HPP
#define RCOBJECT_HPP
#include <cstddef>
class RCObject{
private:
 std::size_t refCount; //计数变量
 bool shareable; //被计数对象(即值对象)是否可以共享的标识,默认可以共享
public:
 RCObject():refCount(0),shareable(true){
 }
 RCObject(RCObject const& rhs) : refCount(0),shareable(true){
 }
 RCObject& operator=(RCObject const& rhs){
  return *this;
 }
 virtual ~RCObject()=0; //作为基类使用,则析构函数一般要声明为vritual
                              //注意纯虚的虚构函数必须提供实现
 void addReference(){
  ++refCount;
 }
 void removeReference(){
  if(--refCount==0)
   delete this; //使用时继承RCObject的值对象(即被计数的对象)必须创建在堆上
 }
 void markUnshareable(){
  shareable=false;
 }
 bool isShareable() const{ //值对象是否可共享
  return shareable;
 }
 bool isShared() const{ //值对象是否已经被共享
  return refCount>1;
 }
};
RCObject::~RCObject(){ //纯虚的析构函数必须有定义
}
#endif

    解释:
    (1)我们声明析构函数为纯虚的,因此RCObject是一个抽象类,不能实例化,只能由值类来继承。同时,RCObject类中不单单只是接口的声明,它也有计数功能的实现,它们是值类共性(都需要计数)的抽离,这样的类一般称为抽象混合基类。注意当把析构函数声明为纯虚的时,必须同时要有定义,因为子类一定会调用基类的析构函数,这样它就必须有定义,而不只是声明。
    (2)这是一个侵入式的计数器类,因为值对象继承自RCObject,计数器就成为了值对象的一部分,要占用值对象的内存空间。通过继承,值对象自身有了计数功能和共享开关。
    (3)这里增加了一个共享标志shareable,表示值对象能不能共享。如果值对象不想让多个主对象共享,就可以用markUnshareable()关闭这个标志。isShared()表示对象是否已经被共享。引入共享标志后就可以实现写时拷贝。比如对String类,访问可能会是读操作cout<<s2[2],也可能是写操作s2[2]='x'。如果是写操作,则在opeartor[]函数中可先判断值对象是否已经被共享,如果已经被共享,则必须拷贝一份出来进行写操作。
    (4)引用计数为0时,删除对象用delete this,这就要求值对象必须分配在堆上,可见,引用计数的抽象基类实现是有限制的。当然,我们用模板并且通过组合的方式来实现引用计数可以更完善,也更高效。这里主要是为了演示Interface Class和Handle Class的设计策略。
    2、封装值对象的智能指针。 由于我们需要在主类中组合一个值类(有引用计数功能)的指针,通过这个指针调用各个计数操作来完成值对象的计数功能,例如在String类中通过StringValue* data指针来调用各个计数操作,这会在主类中增加很多函数调用代码。为此,我们可以把这个值类指针data封装成智能指针,由智能指针类来完成所有对计数函数的调用,只要在主类中组合一个指向值类的智能指针对象(而不组合原始的值类指针)即可。当然,我们可以直接复用在“智能指针”介绍中开发的通用型智能指针,但这里的智能指针对所指类型有要求(是值类,有计数功能),具有特殊性,并不能直接用通用型的智能指针。因此,我们独立实现了一个简单的智能指针RCPtr:

view plaincopy to clipboardprint?
//rcptr.hpp:简单的智能指针,用于指向有计数功能的对象(值对象)  
#ifndef RCPTR_HPP  
#define RCPTR_HPP  
template<typename T>  
class RCPtr{ //T必须为值类,即T必须继承自RCObject  
private:  
    T* pointee; //哑指针  
    void init(){  
        if(pointee==0)  
            return;  
        //如果值对象不可共享  
        if(pointee->isShareable()==false)  
            pointee=new T(*pointee); //则只能对它进行拷贝,不能共享它  
        pointee->addReference(); //现在有一个新的主对象(即组合了本智能指针的主对象)引用了  
                                 //这个值对象,因此引用计数要加1  
    }  
public:  
    RCPtr(T* realPtr=0):pointee(realPtr){ //智能指针的构造  
        //产生一个智能指针,说明有了一个值对象  
        //引用计数会加1  
        init();  
    }  
    RCPtr(RCPtr const& rhs):pointee(rhs.pointee){ //智能指针的拷贝  
        init();  
    }  
    RCPtr& operator=(RCPtr const& rhs){ //智能指针的赋值  
        if(pointee==rhs.pointee)  
            return *this; //自我赋值情况  
        T* oldPointee=pointee; //保存原来的指针  
        pointee=rhs.pointee; //实行赋值,指向了新的值对象  
        init(); //引用计数加1,如果值对象不能共享,则要进行拷贝  
        if(oldPointee) //原来指向的值对象的引用计数减1,如果变成0,会销毁对象  
            oldPointee->removeReference();  
        return *this;  
    }  
    ~RCPtr(){  
        if(pointee) //值对象引用计数减1,如果变成0,则销毁对象  
            pointee->removeReference();  
    }  
    T* operator->() const{  
        return pointee; //返回哑指针  
    }  
    T& operator*() const{  
        return *pointee; //返回值对象  
    }  
};  
#endif 
//rcptr.hpp:简单的智能指针,用于指向有计数功能的对象(值对象)
#ifndef RCPTR_HPP
#define RCPTR_HPP
template<typename T>
class RCPtr{ //T必须为值类,即T必须继承自RCObject
private:
 T* pointee; //哑指针
 void init(){
  if(pointee==0)
   return;
  //如果值对象不可共享
  if(pointee->isShareable()==false)
   pointee=new T(*pointee); //则只能对它进行拷贝,不能共享它
  pointee->addReference(); //现在有一个新的主对象(即组合了本智能指针的主对象)引用了
                           //这个值对象,因此引用计数要加1
 }
public:
 RCPtr(T* realPtr=0):pointee(realPtr){ //智能指针的构造
  //产生一个智能指针,说明有了一个值对象
  //引用计数会加1
  init();
 }
 RCPtr(RCPtr const& rhs):pointee(rhs.pointee){ //智能指针的拷贝
  init();
 }
 RCPtr& operator=(RCPtr const& rhs){ //智能指针的赋值
  if(pointee==rhs.pointee)
   return *this; //自我赋值情况
  T* oldPointee=pointee; //保存原来的指针
  pointee=rhs.pointee; //实行赋值,指向了新的值对象
  init(); //引用计数加1,如果值对象不能共享,则要进行拷贝
  if(oldPointee) //原来指向的值对象的引用计数减1,如果变成0,会销毁对象
   oldPointee->removeReference();
  return *this;
 }
 ~RCPtr(){
  if(pointee) //值对象引用计数减1,如果变成0,则销毁对象
   pointee->removeReference();
 }
 T* operator->() const{
  return pointee; //返回哑指针
 }
 T& operator*() const{
  return *pointee; //返回值对象
 }
};
#endif

    解释:
    (1)RCPtr的构造。在根据传来的值对象指针构造RCPtr对象时,需要判断这个值对象是否可以共享,如果它设为不可共享,那就不能简单地增加引用计数了,而是只能只能对它进行拷贝,这样RCPtr就指向了一个独立值对象,而没有共享原来的那个值对象。因为RCPtr被组合在主对象中,说明这个独立值对象有一个主对象引用了它,因此引用计数要加1。如果可以共享,则直接增加引用计数即可。
    (2)RCPtr的拷贝也一样。把主对象a拷贝给主对象b时,a中的RCPtr成员也会拷贝给b。我们知道a和b需要共享a中的值对象,而值对象被封装在RCPtr中(让RCPtr成为主对象的一个成员,而不是赤裸裸的值对象),因此RCPtr的拷贝只要增加值对象的引用计数即可,并没有拷贝RCPtr指向的值对象(除非它不可共享,这时就必须深拷贝指向的值对象,使a和b拥有各自独立的值对象)。
    (3)RCPtr的赋值。主对象赋值a=b时,会导致里面的RCPtr成员的赋值。因为赋值时a可以共享b内部的值对象以提高性能,这需要先保存a中的RCPtr,然后把b的RCPtr指针(即内部的哑指针)赋给a,这样a就要使用b内部的值对象了,但接着要调用init(),或者是直接增加b内部的那个值对象的引用计数,或者是因为它不可共享,需要拷贝一份出来给a。最后a原来引用的值对象的计数要减1(因为主对象a已经不再引用它了)。可见,值对象拥有了共享否决权后使得引用计数的实现变得复杂了。因为值对象可以声明它不想在各个主对象之间被共享,因此我们需要增加额外的代码来判断它是否可共享,而不是在主对象拷贝或赋值时直接简单地增加值对象的引用计数。
    (4)解引用操作符operator*必须返回T&型引用,不能返回T型对象,因为哑指针pointee可能指向了派生类对象,如果返回T型对象,会导致对象切割问题。同理,箭头操作符opeator->也必须返回T*型指针。
    3、使用了引用计数的String类实现。 我们使用pimpl手法,把String内的char*型数据封装成独立的StringValue值类,在String类中用一个指针成员(这里使用封装StringValue的智能指针RCPtr<StringValue>)指向String的数据。StringValue类有引用计数功能,因此要继承自RCObject,多个String对象在 拷贝或赋值时可以共享一个StringValue值对象。

view plaincopy to clipboardprint?
//string.hpp:字符串类,对字符串对象的内容进行了引用计数,可使多个字符串  
//对象共享同一个字符串值  
#ifndef STRING_HPP  
#define STRING_HPP  
#include <iostream>  
#include <cstring>  
#include "rcobject.hpp"  
#include "rcptr.hpp"  
class String{  
private:  
    //表示字符串内容的内嵌类,实现了引用计数功能  
    //这个值对象必须在堆上创建  
    struct StringValue : public RCObject{  
        char *data;  
        void init(char const* initValue){  
            data=new char[strlen(initValue)+1];  
            strcpy(data,initValue); //对字符串进行拷贝  
        }  
        StringValue(char const *initValue){ //值对象的构造  
            init(initValue);  
        }  
        StringValue(StringValue const& rhs){ //值对象的拷贝  
            init(rhs.data);  
        }  
        ~StringValue(){  
            delete[] data;  
        }         
    };  
    RCPtr<StringValue> value; //String对象的内容,用智能指针RCPtr封装它  
    friend std::ostream& operator<<(std::ostream&,String const&);  
public:  
    String(char const* initValue="")  
        :value(new StringValue(initValue)){  
    }  
    char const& operator[](int index) const{ //const版本:只可能是读操作  
        return value->data[index];  
    }  
    char& operator[](int index){ //非const版本:可能是读也可能是写  
                                 //因此需要完成写时拷贝  
        if(value->isShared()){ //如果已经被共享了  
            value=new StringValue(value->data); //则必须拷贝一份出来进行写操作  
        }  
        value->markUnshareable(); //标记为不可共享  
        return value->data[index];   
    }  
};  
inline std::ostream& operator<<(std::ostream& os,String const& str){  
    os<<(str.value)->data;  
    return os;  
}  
#endif 
//string.hpp:字符串类,对字符串对象的内容进行了引用计数,可使多个字符串
//对象共享同一个字符串值
#ifndef STRING_HPP
#define STRING_HPP
#include <iostream>
#include <cstring>
#include "rcobject.hpp"
#include "rcptr.hpp"
class String{
private:
 //表示字符串内容的内嵌类,实现了引用计数功能
 //这个值对象必须在堆上创建
 struct StringValue : public RCObject{
  char *data;
  void init(char const* initValue){
   data=new char[strlen(initValue)+1];
   strcpy(data,initValue); //对字符串进行拷贝
  }
  StringValue(char const *initValue){ //值对象的构造
   init(initValue);
  }
  StringValue(StringValue const& rhs){ //值对象的拷贝
   init(rhs.data);
  }
  ~StringValue(){
   delete[] data;
  }  
 };
 RCPtr<StringValue> value; //String对象的内容,用智能指针RCPtr封装它
 friend std::ostream& operator<<(std::ostream&,String const&);
public:
 String(char const* initValue="")
  :value(new StringValue(initValue)){
 }
 char const& operator[](int index) const{ //const版本:只可能是读操作
  return value->data[index];
 }
 char& operator[](int index){ //非const版本:可能是读也可能是写
                              //因此需要完成写时拷贝
  if(value->isShared()){ //如果已经被共享了
   value=new StringValue(value->data); //则必须拷贝一份出来进行写操作
  }
  value->markUnshareable(); //标记为不可共享
  return value->data[index];
 }
};
inline std::ostream& operator<<(std::ostream& os,String const& str){
 os<<(str.value)->data;
 return os;
}
#endif

view plaincopy to clipboardprint?
//stringtest.cpp:对String的测试  
#include <iostream>  
#include "string.hpp"  
using namespace std;  
int main(){  
    String s1("abcd");  
    cout<<s1<<endl;  
    String s2("efgh");  
    s2=s1; //s2和s1共享"abcd"  
    cout<<s2<<endl;  
    const String s3(s1); //s1,s2,s3共享“abcd"  
    cout<<s3<<endl;  
    cout<<s3[2]<<endl; //读操作,调用const版本的opeator[],  
                       //不会进行拷贝  
    cout<<s2[2]<<endl; //读操作,调用non-const版本的opeator[],  
                       //会进行拷贝  
    s2[2]='x'; //写操作,调用non-const版本的opeator[],  
               //会进行拷贝,即写时拷贝  
    cout<<s2<<endl; //输出修改后的值  
    return 0;  
}                                 
//stringtest.cpp:对String的测试
#include <iostream>
#include "string.hpp"
using namespace std;
int main(){
 String s1("abcd");
 cout<<s1<<endl;
 String s2("efgh");
 s2=s1; //s2和s1共享"abcd"
 cout<<s2<<endl;
 const String s3(s1); //s1,s2,s3共享“abcd"
 cout<<s3<<endl;
 cout<<s3[2]<<endl; //读操作,调用const版本的opeator[],
                    //不会进行拷贝
 cout<<s2[2]<<endl; //读操作,调用non-const版本的opeator[],
                    //会进行拷贝
 s2[2]='x'; //写操作,调用non-const版本的opeator[],
            //会进行拷贝,即写时拷贝
 cout<<s2<<endl; //输出修改后的值
 return 0;
}                              

    解释:
    (1)这里值类StringValue封装了String的数据和实现细节,可见我们把Stirng的接口与实现细节分离了。它被实现为内嵌类,继承自RCObject,有引用计数功能。它表示是专为String设计的,只在String内部使用。
    (2)这里使用了智能指针RCPtr来封装StringValue值对象,这样String的实现就非常简洁,没有任何的计数操作的调用代码。当String对象进行拷贝或赋值时,需要对里面的StringValue值对象进行引用计数操作,所有的这些操作都委托给了智能指针RCPtr<StringValue>对象,它既含有实际的StringValue值对象,又能调用StringValue的计数函数完成实际的计数操作。String里面没有了指针成员,都是对象成员,因此无需实现拷贝构造函数、赋值运算符、析构函数等,使用默认的就够了,一切都非常的简洁。
    (3)const版本的operator[]只能是读操作,因此不需要实现写时拷贝。而non-const版的operator[]可能是读cout<<s2[2],也可能是写s2[2]='x'。如果是写,而对象已经被共享了,则必须进行拷贝了,我们对拷贝出来的一份值对象进行写操作,就需要把它标记为不可共享,这种情况下使得这个拷贝出来的值对象永远不可共享了。另一方面,我们也没有区分出读和写操作,如果这个operator[]调用是读操作(比如cout<<s2[2]),那我们同样也进行了拷贝,而实际上在读的时候并不需要拷贝,可见这样的写时拷贝实现并不是完美的,它导致有时在读的时候也进行了拷贝。事实上,opeator[]运算并不能区分是读cout<<s2[2],还是写s2[2]='x',因为opeartor[]函数里只是返回一个值的引用,实际的写操作并不是在函数里面完成的。要区分读和写,我们需要使用代理类技术(即代理模式)。
    4、为既有类添加引用计数。 前面我们设计StringValue时,让它继承RCObject,使它有了引用计数功能。可见引用计数是一种通用的功能,任何类(不一定要作为值类)只要继承RCObject,就拥有了引用计数功能。现在如果程序库中已经存在一个类Widget,它不能被修改,但客户端又需要它具有引用计数功能,该怎么办呢?唯一的办法就是把引用计数功能委托给其他类的完成。我们可以对Widget进行包装,比如通过组合把Widget包装成RCWidget类,让RCWidget类组合一个指向Widget对象的RCPtr智能指针来完成Widget的所有功能,同时增加引用计数功能。我们把RCWidget提供给客户端使用就可以了。最直接的实现是让RCWidget继承RCObject,但这样的话RCWidget对象就只能创建在堆上了。其实我们可以让RCPtr来完成对Widget的引用计数。这就需要修改RCPtr的设计,其实并不难。只要在RCPtr中引入一个内嵌类,让它继承RCObject,用这个内嵌类来对作为模板实参传过去的Widget类进行引用计数,修改后的智能指针为RCIPtr类。

view plaincopy to clipboardprint?
//rciptr.hpp:指向值对象的智能指针,同时可以对值对象进行引用计数  
#ifndef RCIPTR_HPP  
#define RCIPTR_HPP  
#include "rcobject.hpp"  
template<typename T>  
class RCIPtr{  
private:  
    //由内嵌类来持有值对象,并能对值对象进行引用计数  
    struct CountHolder : public RCObject{  
        T* pointee;  
        ~CountHolder(){  
            delete pointee;  
        }  
    };  
    CountHolder *counter; //持有者:指向值对象,并且有引用计数功能  
    void init(){  
        //如果对象不能共享,则只能对它进行拷贝  
        if(counter->isShareable()==false){  
            T* oldValue=counter->pointee; //保存原来的指针  
            counter=new CountHolder; //创建一个新的持有者(有引用计数功能)  
            //对值对象进行拷贝  
            counter->pointee=oldValue ? new T(*oldValue) : 0;  
        }  
        counter->addReference(); //引用计数加1  
    }  
public:  
    RCIPtr(T* realPtr=0) : counter(new CountHolder){ //智能指针的构造  
        //创建一个持有者  
        counter->pointee=realPtr; //让持有者指向值对象  
    }  
    RCIPtr(RCIPtr<T> const& rhs) : counter(rhs.counter){ //智能指针的拷贝  
        init();  
    }  
    RCIPtr<T>& operator=(RCIPtr<T> const& rhs){ //智能指针的赋值  
        if(counter!=rhs.counter){  
            counter->removeReference(); //原来引用计数减1  
            counter=rhs.counter; //进行赋值,指向新的值对象  
            init(); //引用计数加1,如果值对象不能共享,则要进行拷贝  
        }  
        return *this;  
    }  
    ~RCIPtr(){  
        counter->removeReference();  
    }  
    T* operator->() const{  
        return counter->pointee; //返回哑指针  
    }  
    T& operator*() const{  
        return *(counter->pointee); //返回值对象  
    }  
    RCObject& getRCObject() const{ //返回值对象,客户可以判断它是否被共享  
                                   //这里直接调用上面的operator*  
        return *counter;  
    }  
};  
#endif 
//rciptr.hpp:指向值对象的智能指针,同时可以对值对象进行引用计数
#ifndef RCIPTR_HPP
#define RCIPTR_HPP
#include "rcobject.hpp"
template<typename T>
class RCIPtr{
private:
 //由内嵌类来持有值对象,并能对值对象进行引用计数
 struct CountHolder : public RCObject{
  T* pointee;
  ~CountHolder(){
   delete pointee;
  }
 };
 CountHolder *counter; //持有者:指向值对象,并且有引用计数功能
 void init(){
  //如果对象不能共享,则只能对它进行拷贝
  if(counter->isShareable()==false){
   T* oldValue=counter->pointee; //保存原来的指针
   counter=new CountHolder; //创建一个新的持有者(有引用计数功能)
   //对值对象进行拷贝
   counter->pointee=oldValue ? new T(*oldValue) : 0;
  }
  counter->addReference(); //引用计数加1
 }
public:
 RCIPtr(T* realPtr=0) : counter(new CountHolder){ //智能指针的构造
  //创建一个持有者
  counter->pointee=realPtr; //让持有者指向值对象
 }
 RCIPtr(RCIPtr<T> const& rhs) : counter(rhs.counter){ //智能指针的拷贝
  init();
 }
 RCIPtr<T>& operator=(RCIPtr<T> const& rhs){ //智能指针的赋值
  if(counter!=rhs.counter){
   counter->removeReference(); //原来引用计数减1
   counter=rhs.counter; //进行赋值,指向新的值对象
   init(); //引用计数加1,如果值对象不能共享,则要进行拷贝
  }
  return *this;
 }
 ~RCIPtr(){
  counter->removeReference();
 }
 T* operator->() const{
  return counter->pointee; //返回哑指针
 }
 T& operator*() const{
  return *(counter->pointee); //返回值对象
 }
 RCObject& getRCObject() const{ //返回值对象,客户可以判断它是否被共享
                                //这里直接调用上面的operator*
  return *counter;
 }
};
#endif

view plaincopy to clipboardprint?
//rcwidget.hpp:RCWidget对Widget进行包装,通过智能指针RCIPtr使之具有引用计数功能  
#ifndef RCWIDGET_HPP  
#define RCWIDGET_HPP  
#include <iostream>  
#include "string.hpp"  
#include "rciptr.hpp"  
class Widget{ //容器部件类:已有的不能修改的类  
private:  
    int isize;  
    String stitle;  
    friend std::ostream& operator<<(std::ostream&,Widget const&);  
public:  
    Widget(int size=0):isize(size){  
        //...  
    }  
    Widget(String title="Untitled"):stitle(title){  
        //...  
    }  
    void doThis(){ //非const操作  
        //...  
    }  
    int showThat() const{ //const操作  
        return isize;  
        //...  
    }  
    //...  
};  
inline std::ostream& operator<<(std::ostream& os,Widget const& rhs){  
    os<<"Size="<<rhs.isize<<",Title="<<rhs.stitle; //输出窗口大小和标题  
    return os;  
}  
 
//对Widget添加了引用计数功能后的包装类  
class RCWidget{   
private:  
    RCIPtr<Widget> value; //指向Widget对象的智能指针,同时还能对Widget进行计数  
    friend std::ostream& operator<<(std::ostream&,RCWidget const&);  
public:  
    RCWidget(int size=0) : value(new Widget(size)){  
    }  
    RCWidget(String title="Untitled"):value(new Widget(title)){  
    }     
    void doThis(){ //非const操作,说明有可能修改Widget对象的内容,需要写时拷贝  
        if(value.getRCObject().isShared())  
            value=new Widget(*value); //若已经被共享了,则要拷贝一份出来  
        value->doThis(); //调用实际的操作  
    }  
    int showThat() const{ //const操作,不会修改Widget对象,因此直接转发调用  
        return value->showThat();  
    }  
    //...  
};  
inline std::ostream& operator<<(std::ostream& os,RCWidget const& rhs){  
    os<<*(rhs.value);  //输出窗口大小和标题  
    return os;  
}  
#endif  
      
     
//rcwidget.hpp:RCWidget对Widget进行包装,通过智能指针RCIPtr使之具有引用计数功能
#ifndef RCWIDGET_HPP
#define RCWIDGET_HPP
#include <iostream>
#include "string.hpp"
#include "rciptr.hpp"
class Widget{ //容器部件类:已有的不能修改的类
private:
 int isize;
 String stitle;
 friend std::ostream& operator<<(std::ostream&,Widget const&);
public:
 Widget(int size=0):isize(size){
  //...
 }
 Widget(String title="Untitled"):stitle(title){
  //...
 }
 void doThis(){ //非const操作
  //...
 }
 int showThat() const{ //const操作
  return isize;
  //...
 }
 //...
};
inline std::ostream& operator<<(std::ostream& os,Widget const& rhs){
 os<<"Size="<<rhs.isize<<",Title="<<rhs.stitle; //输出窗口大小和标题
 return os;
}

//对Widget添加了引用计数功能后的包装类
class RCWidget{
private:
 RCIPtr<Widget> value; //指向Widget对象的智能指针,同时还能对Widget进行计数
 friend std::ostream& operator<<(std::ostream&,RCWidget const&);
public:
 RCWidget(int size=0) : value(new Widget(size)){
 }
 RCWidget(String title="Untitled"):value(new Widget(title)){
 } 
 void doThis(){ //非const操作,说明有可能修改Widget对象的内容,需要写时拷贝
  if(value.getRCObject().isShared())
   value=new Widget(*value); //若已经被共享了,则要拷贝一份出来
  value->doThis(); //调用实际的操作
 }
 int showThat() const{ //const操作,不会修改Widget对象,因此直接转发调用
  return value->showThat();
 }
 //...
};
inline std::ostream& operator<<(std::ostream& os,RCWidget const& rhs){
 os<<*(rhs.value);  //输出窗口大小和标题
 return os;
}
#endif
 
 

view plaincopy to clipboardprint?
//rcwidgettest.cpp:对RCWidget的测试  
#include <iostream>  
#include "rcwidget.hpp"  
using namespace std;  
int main(){  
    RCWidget w1("Jack"); //标题为"Jack"  
    cout<<w1<<endl; //输出窗口大小和标题内容  
    RCWidget w2("Zhou"); //标题为"Zhou"  
    w2=w1; //w2和w1共享一个Widget对象  
    cout<<w2<<endl;  
      
    const RCWidget w3(w1); //w1,w2,w3共享一个Widget对象  
    cout<<w3<<endl;  
    return 0;  
}                                 
//rcwidgettest.cpp:对RCWidget的测试
#include <iostream>
#include "rcwidget.hpp"
using namespace std;
int main(){
 RCWidget w1("Jack"); //标题为"Jack"
 cout<<w1<<endl; //输出窗口大小和标题内容
 RCWidget w2("Zhou"); //标题为"Zhou"
 w2=w1; //w2和w1共享一个Widget对象
 cout<<w2<<endl;
 
 const RCWidget w3(w1); //w1,w2,w3共享一个Widget对象
 cout<<w3<<endl;
 return 0;
}                              

    解释:
    (1)RCIPtr与RCPtr相比,只有两点不同,一是RCIPtr直接指向值对象,现在RCIPtr引入了一个中间层CountHolder类,CountHolder代理对象指向实际的值对象,同时还能对值对象进行引用计数(继承了RCObject)。二是提供一个友好的getRCObject()来返回指向的值对象,当然operator*也是直接返回值对象的,但getRCObject()对客户端而言更友好,因为客户端会经常使用这个函数。
    (2)RCIPtr中的CountHolder对象必须分配在堆上(因此用counter指针),它在计数值变成0时会自己销毁自己(delete this),因此并不需要在RCIPtr的析构函数中delete counter,只需要减少引用计数即可,这一点要特别注意。
    (3)RCWidget包装了Widget,具有Widget的功能,通过RCIPtr它还具有了对Widget的引用计数功能,因此多个RCWidget可以共享一个Widget。当调用非const操作时,说明有可能修改Widget对象的内容,需要写时拷贝。先用getRCObject()获得值对象,看看它是否被共享,若被共享了,则需要拷贝一份出来才能进行修改,然后调用实际的可能做修改动作的Widget操作。当调用const操作时,不会修改Widget对象内容,直接转发调用。RCWidget的实现非常简洁,没有指针成员,因此无需拷贝构造函数、赋值操作符、析构函数等,使用默认就可以了。
    这其实就是Decorator模式的应用。Decorator模式用于动态给一个对象添加一些额外的职责,就扩展功能而言,Decorator模式比生成子类方式更为灵活。我们用RCWidget对Widget进行装饰,动态地给它增加了引用计数功能.

http://blog.csdn.net/zhoudaxia/archive/2009/09/18/4566914.aspx


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/zhoudaxia/archive/2009/09/18/4566914.aspx

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值