我们在做设计时,将接口与实现相分离是一个基本的策略。分离接口与实现主要有两种技术:
(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