上一回讨论的句柄技术有一个明显的缺点:为了将句柄捆绑到类T的对象上,必须要新定义一个具有类型为T的成员对象的新类。
这个毛病相当麻烦,如果想新设计一个类的句柄类,就需要新定义两个类。
C++之父提到过一种定义句柄类的技术可以弥补这一个缺点,主要思想就是将引用技术从数据中分离出来,把引用计数放到句柄类自己的对象之中。
class Handle { public: // 和前面一样 private: Point *p; int *u; };
这里不再有指向UPoint的指针,我们使用指向Point的指针和指向一个int的指针表示引用计数。使用Point* 使得我们不仅能够将一个Handle绑定到一个Point,还能将其绑定到一个继承自Point 的类的对象。
此时我们的Handle类要在构造析构的时候要处理两个指针,比如:
Handle :: Handle() : u(new int(1)), p(new Point ) { } Handle :: Handle(int x, int y) : u( new int(1)), p(new Point(x, y)) { } // 需要正确的增加或减少引用计数 Handle :: Handle(const Handle &h) : u(h.u), p(h.p) { ++*u; } Handle& Handle :: operator = (const Handle &h) { ++*h.u; if ( --*u == 0) { delete u; delete p; } u = h.u; p = h.p; return *this; }
从这些实现可以看出,引用计数与Point类没有能够很好的协同,每次都需要单独处理它们。
如果想让引用计数和Point类更好的协同作用可以对引用计数进行抽象,用一个辅助类来实现引用计数。
class UseCount { public: UseCount(); UseCount(const UseCount &); UseCount & operator = (const UseCount &); ~UseCount(); private: int *p; };
UseCount成员函数的实现就比较简单了:
UseCount :: UseCount() : p(new int(1)) {} UseCount :: UseCount(const UseCount &u) : p(u.p) { ++*p; } UseCount :: ~UseCount { if ( --*p == 0) delete p; }
现在重写Handle类:
class Handle { public: // 和之前一样 private: UseCount u; Point *p; };
现在我们来看成员函数的实现,就相对比之前简单了:
Handle :: Handle() : p(new Point) { } Handle :: Handle(int x, int y) : p(new Point(x,y)) { } Handle :: Handle(const Point & p0) : p(new Point(p0)) { } Handle :: Handle(const Handle & h) : u(h.u), p(h.p) { }
在写析构函数的时候,我们需要判断引用计数是否为0,以便知道要不要删除句柄的数据。
我们可以让UseCount 类来提供这个数据,通过一个类成员方法来藐视UseCount 对象是不是唯一指向它的计数器对象:
class UseCount { public: // 和前面一样 private: bool only(); }; bool UseCount :: only() { return *p == 1; }
当UseCount 有了 only() 成员方法,Handle类的析构函数就可以这样写了:
Handle :: ~Handle() { if( u.only() ) delete p; }
当Handle类进行复制操作的时候,我们需要对引用计数值增1,或者减1,可能还要删除一个计数器。
但是现阶段我们设计的UseCount 类和Hanlde 类都不支持上面的操作。注意到我们引入UseCount类的原因是使引用计数的处理和Point 协同起来。因此我们将这些操作放在UseCount中来实现,可以增加下面的成员函数:
bool UseCount :: reattach( const UseCount & u) { ++*u.p; if ( --*p == 0) { delete p; p = u.p; return true; } p = u.p; return false; }
现在有了reattach() 成员方法之后,Handle类的赋值操作可以这样写:
Handle & Handle :: operator = (const Handle & h) { if(u.reattach(h.u)) delete p; p = h.p; return *this; }
最后,如果我们要改变Point (注意前面的一切Handle、UseCount 类都是为了管理和控制Point类,我们真正需要操作的数据其实是Point 类 , 虽然都是在Handle类层进行操作以达到操作Point对象的目的),也就是对Point对象的单个元素进行读取和写入。当有多个句柄关联同一个Point对象时,我们对某个句柄操作需要改变该Point对象,但是其他句柄关联所持有的信息不需要改变,此时我们就需要对Point进行复制(记得前面引入Handle 的作用之一也就是避免不必要的复制,但是这里就需要进行复制了!)。
因此可以再UseCount类中在增加一个成员函数,用来对引用计数进行适当的控制。
bool UseCount :: makeonly() { if ( *p == 1) return false;// 这里的意思就是说,只有一个Handle关联该对象,因此可以直接进行改变,不需要复制对象 --*p; p = new int (1); return true; }
Handle 类的存取函数可以这样写:
int Handle :: x() const { return p->x(); } Handle & Handle :: x(int x0) { if ( u.makeonly() ) p = new Point( *p); p->x(x0); return *this; }
现在列出全部代码:
/* 为了将句柄捆绑到类T的对象上,必须要新定义一个具有类型为T的成员对象的新类。 这个毛病相当麻烦,如果想新设计一个类的句柄类,就需要新定义两个类。 C++之父提到过一种定义句柄类的技术可以弥补这一个缺点,主要思想就是将引用技术从数据中分离出来, 把引用计数放到句柄类自己的对象之中。 这个策略的一个重要优势:UseCount类可以在不了解其使用者任何信息的情况下与之合为一体。这样一来, 我们就可以把这个类当成句柄实现的一部分,与各种不同的数据结构协同工作。 */ #include <iostream> using namespace std; //----------------------------------------- class Point { private: int xval,yval; public: Point():xval(0),yval(0){} Point(int x,int y):xval(x),yval(y){} int x()const{return xval;} int y()const{return yval;} Point& x(int xv){xval=xv;return *this;} Point& y(int yv){yval=yv;return *this;} }; //------------------------------------------------------ class UseCount { private: int* p; public: UseCount(); UseCount(const UseCount&); UseCount& operator=(const UseCount&); ~UseCount(); bool only(); bool reattach(const UseCount&); bool make_only(); }; UseCount::UseCount():p(new int(1)){} UseCount::UseCount(const UseCount&u):p(u.p){++*p;} UseCount::~UseCount() { if (--*p==0) { delete p; } } bool UseCount::only() { return *p==1; } bool UseCount::reattach(const UseCount& u) { ++*u.p; if (--*p==0) { delete p; p=u.p; return true; } p=u.p; return false; } bool UseCount::make_only() { if (*p==1) return false; --*p; p=new int(1); return true; } //------------------------------------------- class Handle { private: Point* p; UseCount u; public: Handle(); Handle(int,int); Handle(const Point&); Handle(const Handle&); Handle& operator =(const Handle&); ~Handle(); int x()const; Handle&x(int); int y()const; Handle&y(int); }; Handle::Handle():p(new Point){} Handle::Handle(int x,int y):p(new Point(x,y)){} Handle::Handle(const Point&p0):p(new Point(p0)){} Handle::Handle(const Handle&h):u(h.u),p(h.p){} Handle::~Handle() { if (u.only()) { delete p; } } Handle& Handle::operator=(const Handle &h) { if (u.reattach(h.u)) delete p; p=h.p; return *this; } int Handle::x()const { return p->x(); } int Handle::y()const { return p->y(); } Handle& Handle::x(int x0) { if (u.make_only()) p=new Point(*p); p->x(x0); return *this; } Handle& Handle::y(int y0) { if (u.make_only()) p=new Point(*p); p->y(y0); return *this; } //--------------------------------------------------- int main() { Handle h(3,4); Handle h2 = h; h2.x(5); int n = h.x(); cout<<n<<endl; return 0; }
运行结果为:3